te')); return $arr; } /* 遍历用户所有主题 * @param $uid 用户ID * @param int $page 页数 * @param int $pagesize 每页记录条数 * @param bool $desc 排序方式 TRUE降序 FALSE升序 * @param string $key 返回的数组用那一列的值作为 key * @param array $col 查询哪些列 */ function thread_tid_find_by_uid($uid, $page = 1, $pagesize = 1000, $desc = TRUE, $key = 'tid', $col = array()) { if (empty($uid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('uid' => $uid), array('tid' => $orderby), $page, $pagesize, $key, $col); return $arr; } // 遍历栏目下tid 支持数组 $fid = array(1,2,3) function thread_tid_find_by_fid($fid, $page = 1, $pagesize = 1000, $desc = TRUE) { if (empty($fid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('fid' => $fid), array('tid' => $orderby), $page, $pagesize, 'tid', array('tid', 'verify_date')); return $arr; } function thread_tid_delete($tid) { if (empty($tid)) return FALSE; $r = thread_tid__delete(array('tid' => $tid)); return $r; } function thread_tid_count() { $n = thread_tid__count(); return $n; } // 统计用户主题数 大数量下严谨使用非主键统计 function thread_uid_count($uid) { $n = thread_tid__count(array('uid' => $uid)); return $n; } // 统计栏目主题数 大数量下严谨使用非主键统计 function thread_fid_count($fid) { $n = thread_tid__count(array('fid' => $fid)); return $n; } ?>javascript - Chained jQuery promises with abort - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Chained jQuery promises with abort - Stack Overflow

programmeradmin3浏览0评论

I'm currently writing API code which, several layers deep, wraps $.ajax() calls.

One requirement is that the user must be able to cancel any request (if it's taking too long, for example).

Normally this is acplished via something simple, like:

var jqXHR = $.ajax(..);
$(mycancelitem).click(function () {
     jqXHR.abort();
});

However my code looks more like this:

function myapicall() {

    var jqxhr = $.ajax(…);
    var prms = def.then(function (result) {
        // modify the result here
        return result + 5;
    });

    return prms;
}

The problem here is someone calling myapicall() only gets a jQuery.Promise with no way to abort it. And while the sample above is very simple, in my actual code there are several layers of chaining, in many places.

Is there a solution to this?

I'm currently writing API code which, several layers deep, wraps $.ajax() calls.

One requirement is that the user must be able to cancel any request (if it's taking too long, for example).

Normally this is acplished via something simple, like:

var jqXHR = $.ajax(..);
$(mycancelitem).click(function () {
     jqXHR.abort();
});

However my code looks more like this:

function myapicall() {

    var jqxhr = $.ajax(…);
    var prms = def.then(function (result) {
        // modify the result here
        return result + 5;
    });

    return prms;
}

The problem here is someone calling myapicall() only gets a jQuery.Promise with no way to abort it. And while the sample above is very simple, in my actual code there are several layers of chaining, in many places.

Is there a solution to this?

Share Improve this question edited Feb 13, 2014 at 23:22 Bergi 665k161 gold badges1k silver badges1.5k bronze badges asked Feb 13, 2014 at 21:59 automatonautomaton 3,1285 gold badges30 silver badges40 bronze badges 7
  • myapicall() returns the jqXHR object. Why wouldn't var api = myapicall(); api.abort(); work? Also, I don't think your return result + 5; does anything. – gen_Eric Commented Feb 13, 2014 at 22:03
  • 1 the line def = def.then(...) means it's a chained promise. if you try it in code, you'll see what you get back is not a jqXHR, but rather a real jQuery.Promise, without abort. it doesn't return the jqXHR from the ajax request, but the chained promise. – automaton Commented Feb 13, 2014 at 22:06
  • Ah! I didn't notice that. What if you removed the def= before def.then()? Would that work? – gen_Eric Commented Feb 13, 2014 at 22:09
  • 1 A solution would be to use a proper Promise library that does support cancellation and can assimilate jQuery deferreds. Unfortunately, I can't name one yet; but maybe you have more luck at searching. – Bergi Commented Feb 13, 2014 at 23:32
  • 1 Hi @automaton, any news with this question? I stumbled at same problem – Pavel 'Strajk' Dolecek Commented Nov 25, 2014 at 9:37
 |  Show 2 more ments

5 Answers 5

Reset to default 4

My solution inspired from all the

With jQuery promises

client.js

var self = this;
function search() {
  if (self.xhr && self.xhr.state() === 'pending') self.xhr.abort();
  var self.xhr = api.flights(params); // Store xhr request for possibility to abort it in next search() call
  self.xhr.then(function(res) {
    // Show results
  });
}
// repeatedly call search()

api.js

var deferred = new $.Deferred();
var xhr = $.ajax({ });
xhr.then(function (res) { // Important, do not assign and call .then on same line
  var processed = processResponse(res);
  deferred.resolve(processed);
});
var promise = deferred.promise();
promise.abort = function() {
  xhr.abort();
  deferred.reject();
};
return promise;

With Q + jQuery promises

client.js

var self = this;
function search() {
  if (self.xhr && self.xhr.isPending()) self.xhr.abort();
  var self.xhr = api.flights(params); // Store xhr request for possibility to abort it in next search() call
  self.xhr.then(function(res) {
    // Show results
  });
}
// repeatedly call search()

api.js

var deferred = Q.defer();
var xhr = $.ajax({ });
xhr.then(function (res) { // Important, do not assign and call .then on same line
  var processed = processResponse(res);
  deferred.resolve(processed);
});
deferred.promise.abort = function() {
  xhr.abort();
  deferred.reject();
};
return deferred.promise;

you could return a object that has both the jqXHR and promise

function myapicall() {

    var jqXHR = $.ajax(..);
    var promise = jqXHR.then(function (result) {
        // modify the result here
        return result + 5;
    });

    return {jqXHR:jqXHR,promise:promise};
}

Basically, you have you make your own promise which will represent the entire operation and add a special abort function to it. Something like the following:

function myapicall() {
    var currentAjax = $.ajax({ ... })
        .then(function(data) {
            ...
            return currentAjax = $.ajax({ ... });
        },
        function(reason) { wrapper.reject(reason); })
        .then(...)
        .then(...)
        .then(...)
        .then(...)
        .then(...)
        .then(...)
        .then(...)
        .then(...)
        .then(...)
        .then(...)
        .then(function(data) {
            ...
            wrapper.resolve(data);
        },
        function(reason) { wrapper.reject(reason); });

    // haven't used jQuery promises, not sure if this is right
    var wrapper = new $.Deferred();
    wrapper.promise.abort = function() {
        currentAjax.abort();
        wrapper.reject('aborted');
    };
    return wrapper.promise;
}

This pattern (updating the currentAjax variable) must be continued at each stage of the $.ajax chain. In the last AJAX call, where everything has finally been loaded, you will resolve the wrapper promise with whatever data you wish.

Looked for solution to similar problem and solved it like this:

var xhr = $.ajax({ });
return xhr.then(function (res) { 
  var processed = processResponse(res);
  return $.Deferred().resolve(processed).promise();
}).promise(xhr);            // this is it. Extends xhr with new promise 

This will return standard jqXHR object with abort and such + all promise functions.

Note that you may or may not want to return .promise(xhr) from .then() as well. Depends on how you want to treat response in .done() functions from API.

I worked around this using a set of three helpers. It's pretty much manual and you must pay attention to really not forget to use the helpers in your API's implementation, or else well... abort won't abort.

function abort(prom) {
    if( prom["abort"] ) {
        prom["abort"]();
    } else {
        throw new Error("Not abortable");
    }
}

function transfer_abort_from_one(abortable, other) {
    other["abort"] = function() { abortable.abort() };
}

function transfer_abort_from_many(abortables, other) {
    other["abort"] = function() {
        for(let p of abortables) {
            if(p["abort"]) {
                p["abort"]();
            }
        }
    };
}

Now

function api_call() {
    let xhr = $.ajax(...);
    let def = $.Deferred();
    // along these lines
    xhr.fail( def.reject );
    xhr.done( def.resolve );
    // then
    let prom = def.promise();
    transfer_abort_from_one(xhr, prom);
    return prom;
}


function api_call2() {
    let xhrs = [$.ajax(...), $.ajax(...)];
    let prom = $.when.apply(null, xhrs);
    transfer_abort_from_many(xhrs, prom);
    return prom;
}

Using your API and aborting:

var api_prom = api_call();
abort(api_prom);

var api_prom2 = api_call2();
abort(api_prom2);

Note that depending on how your API is built, you must not forget to transfer the abort from the lower layers promises to the higher layer promises. All this is error-prone. It would be a lot better if JQuery (for example) did the transfer when when() or then() get called.

发布评论

评论列表(0)

  1. 暂无评论