如何在自定义函数中使用jQuery的promise/deferred?

37

我有一个通过 navigator.geolocation 获取位置的函数:

var getLocation = function( callback ){

    navigator.geolocation.getCurrentPosition( callback || function( position ){

        // Stuff with geolocation

    });

};

我希望能够使用jQuery的Deffered对象链式调用此函数,但我仍然没有掌握Deffered的概念和用法。

我正在寻找类似于这个伪代码的东西:

getLocation().then(function(){
    drawMarkerOnMap();
});

如果没有翻转过来并在代码中淹死,这种语法是否可能?

3个回答

67
你需要实例化一个新的延迟对象,并从函数中返回它(或它的Promise)。一旦获得响应,请调用它的 .resolve 方法:
你必须实例化一个新的deferred对象并返回它(或其promise)从函数中。一旦收到响应,调用其 .resolve 方法:
var getLocation = function() {
    var deferred = new $.Deferred();

    navigator.geolocation.getCurrentPosition(function( position ){
        // Stuff with geolocation
        deferred.resolve(position);
    });

    // return promise so that outside code cannot reject/resolve the deferred
    return deferred.promise();
};

使用方法:

getLocation().then(drawMarkerOnMap);

参考: jQuery.Deferred


补充:

我建议不要同时使用延迟对象和将回调函数传递给函数的方法,以保持接口简单。但如果你必须保持向后兼容性,你可以简单地在延迟对象上注册传递的回调函数:

var getLocation = function(callback) {
    var deferred = new $.Deferred();

    if ($.isFunction(callback)) {
        deferred.then(callback);
    }

    navigator.geolocation.getCurrentPosition(function( position ){
        // Stuff with geolocation
        deferred.resolve(position);
    });

    // return promise so that outside code cannot reject/resolve the deferred
    return deferred.promise();
};

谢谢您的回复和精彩的答案! 当您说“..建议不要同时使用延迟对象和传递回调函数..”时,您会如何支持诸如getCurrentPosition之类的异步请求呢? - hitautodestruct
2
@hitautodestruct:不,我的意思是让getLocation接受回调函数并且返回延迟对象。也就是说,有两种方式,要么foo(bar),要么foo().then(bar)。调用该函数应该只有一种方式。 - Felix Kling
如果您不使用/需要jquery,请查看此答案 - hitautodestruct

2

尽管上面的例子对我有所帮助,但我还需要更多的阅读来理解这个概念。

下面是一个基于我的代码的示例,其中包含注释,以便在我回来时帮助我,希望任何阅读此Stackoverflow问题的人都能受益:

/* promise based getFilter to accommodate getting surrounding suburbs */
oSearchResult.fPromiseOfFilterSetting = function fPromiseOfFilterSetting(sId) {
    var self = this;
    self.oPromiseCache = self.oPromiseCache || {}; // creates a persistent cache 
                                                   // across function calls
    var oDeferred = $.Deferred(); // `new` keyword is optional
    var oPromise = oDeferred.promise();

    // leverage the cache (it's ok if promise is still pending), you can key
    if (self.oPromiseCache[sId] !== undefined) {
        return self.oPromiseCache[sId];
    }
    else {
        self.oPromiseCache[sId] = oPromise;
    }

    // do our asynchronous action below which at some point calls
    // defered.resolve(...) and hence complete our promise
    $.cmsRestProxy.doAjaxServiceRequest('ocms_searchProperties_Extension', {
        action : 'getSurroundingSuburbs',
        sSuburbIds : 'a0RO0000003BwWeMAK'
    }, function(result, json) {
        console.log("doAjaxServiceRequest(
                       'ocms_searchProperties_Extension')", json);
        oDeferred.resolve(json); // `json` is our result and `.resolve(json)` 
                                 // passes the value as first argument to 
                                 // the `oPromise.done`, `oPromise.fail` 
                                 // and `oPromise.always` callback functions
    })

    // We can now return the promise or attach optional `oPromise.done`,
    // `oPromise.fail`, and `oPromise.always` callbacks which will execute first
    // in the chain.
    //
    // Note that `oPromise.then(doneCallback, failCallback, alwaysCallback)`
    // is short form for the below
    oPromise.done(function(value) { // returned by promise.resolve(...); call
        console.log('will run if this Promise is resolved.', value);
    })
    oPromise.fail(function(value) {
        console.log("will run if this Promise is rejected.", value);
    });
    oPromise.always(function(value) {
        console.log("this will run either way.", value);
    });

    // return a promise instead of deferred object so that
    // outside code cannot reject/resolve it
    return oPromise;
}

// then to use one would do
oSearchResult.fPromiseOfFilterSetting().done(function(value) {alert(value)});

// or using $.when chaining
$.when(
    oSearchResult.fPromiseOfFilterSetting()
)
.done(
      function fDoneCallback(arg1, arg2, argN) {
          console.debug(arguments) // `arguments` is an array of all args collected
      }
);

1
请问您能否按照问题要求使用 navigator.geolocation 吗?否则,这可能只是一段注释良好的代码,而不是一个答案 - Bergi
不,oPromise.then(…)并不等同于oPromise.done().fail().always()。最后一个回调函数是用于progress事件的! - Bergi
这甚至不等同于oPromise.done().fail().progress()then方法是实现承诺的单子功能的基础。 - Bergi
请注意,当您知道参数是一个Promise时,不应使用$.when - Bergi

1

我知道标题中写着jQuery,但当我问这个问题时,promise在Web上还很新,而jQuery是事实上的库。以下是一种更现代的方法,不使用jQuery。

使用原生Promise

所有现代浏览器(除IE11及以下版本;如有需要,请使用polyfill)都支持使用原生Promise构造。

let getLocation = () => {

  return new Promise( ( resolve, reject ) => {

    try {
      navigator.geolocation.getCurrentPosition( position => {
        resolve( position )
      })
    } catch ( err ) {
      reject( err )
    }

  })

};

使用方法:

let runGetLocation = () => { getLocation().then( position => console.log( position ) ) }

您也可以使用ES2016的async/await代替.then()
let runGetLocation = async () => {

  try {
    let position = await getLocation()
    console.log( position )
  } catch ( err ) { console.log( err ) }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接