本节书摘来华章计算机出版社《JavaScript应用程序设计》一书中的第2章,第2.18节,作者:Eric Elliott 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
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(); }); }); 相关资源:Javascript中的异步编程规范Promises/A详细介绍