2.18 Promises与Deferreds
Promises作为一个对象,维护着成功与失败两个回调函数队列。与在异步/同步操作结束后执行的回调函数有所不同,你可以在异步操作的结尾处返回一个包含了一组回调函数的Promises。
Promises能够让你了解当前异步操作的进行状态,是在等待中,还是已完成,具体完成进度是多少。在整个过程中随时都可以向Promises中添加新的回调函数,当异步操作结束时,Promises会被正确解析,此时标识为成功的回调函数队列会被批量执行。
受CommonJS规范Promises/A的启发,jQuery实现了Promises并将这一规范推广至了JavaScript社区。jQuery使用Promises管理自己内部(包括Ajax请求在内)的异步任务队列,事实上,早在jQuery1.5中,所有Ajax方法执行后均会返回一个Promises,来看看它是如何工作的。
var whenDataFetched = $.getJSON(
'https://graph.facebook.com/jsapplications'
);
asyncTest('Ajax promise API', function () {
whenDataFetched
.done(function (response) {
ok(response,
'The server returned data.');
start();
})
.fail(function () {
ok(true,
'There was an error.');
start();
});
});
这个例子展示了,使用Ajax请求去获取“Programming JavaScript Applications”页面的元数据,因为所有jQuery中的Ajax辅助函数都会返回一个Promises,所以,你可以在whenDataFetched.done()中追加你的成功回调函数。
Promises与回调函数间的区别在于,Promises是调用者返回的一个对象,而不是传入调用者随后被执行的一个函数。Promises对象支持在异步操作过程中随时添加新的回调函数,并且会将回调函数彼此间的代码逻辑隔离开,所以使用Promises时回调函数并不一定要在异步操作开始前就传入。
deferred对象包含了一组方法,它被用来管理控制Promises。jQuery的Deferred()方法返回了一个与Promises功能极为类似的对象,这个对象上还拥有resolve()与reject()方法,这两个方法可以触发相应队列中的回调函数。假设你出于灵活性的考虑,想对现有的setTimeout()方法做改造,你想让它随时都可以接受回调函数的传入,而并不是仅能在首次定时调用时添加回调,你可以使用deferred对象来实现这个改造。
var timer = function timer(delay) {
var whenTimedOut = $.Deferred(),
promise = whenTimedOut.promise();
promise.cancel = function (payload) {
whenTimedOut.reject(payload);
};
setTimeout(function () {
whenTimedOut.resolve();
}, delay);
return promise;
};
asyncTest('Deferred', function () {
var startTime = new Date(),
delay = 30,
afterTimeout = 50,
cancelTime = 100,
myTimer = timer(delay),
cancelTimer = timer(cancelTime);
expect(4);
myTimer.done(function () {
ok(true,
'First callback fired.');
});
myTimer.done(function () {
var now = new Date();
ok((now - startTime) > delay,
'Delay works.'
);
});
setTimeout(function () {
ok(true,
'Fires after timeout expires.');
}, afterTimeout);
setTimeout(function () {
start();
}, afterTimeout + 20);
cancelTimer
.done(function () {
ok(false,
'A canceled timer should NOT run .done().');
})
.fail(function () {
ok(true,
'A canceled timer calls .fail().');
})
.cancel();
});
Promises特别适用于那些有着一系列复杂调用序列的异步操作,因为往往在发起新的数据请求之前必须完成对多个数据源的请求。
回调函数会使得代码嵌套逻辑变得越来越深,但拥有了Promises,我们可以在任意数量的Promises对象被解析之后再决定下一步操作,下面是新timer()函数的应用:
var a = timer(60),
b = timer(70),
c = timer(40),
tasks = [a, b, c];
asyncTest('Multiple dependencies.', function () {
$.when(a, b, c).done(function () {
ok(true, 'Runs when all promises resolve');
start();
});
});