转自:廖雪峰的官方网站javascript
在JavaScript的世界中,全部代码都是单线程执行的。java
因为这个“缺陷”,致使JavaScript的全部网络操做,浏览器事件,都必须是异步执行。异步执行能够用回调函数实现:ajax
function callback() { console.log('Done'); } console.log('before setTimeout()'); setTimeout(callback, 1000); // 1秒钟后调用callback函数 console.log('after setTimeout()');
观察上述代码执行,在Chrome的控制台输出能够看到:promise
before setTimeout() after setTimeout() (等待1秒后) Done
可见,异步操做会在未来的某个时间点触发一个函数调用。浏览器
AJAX就是典型的异步操做。以上一节的代码为例:ruby
request.onreadystatechange = function () { if (request.readyState === 4) { if (request.status === 200) { return success(request.responseText); } else { return fail(request.status); } } }
把回调函数success(request.responseText)
和fail(request.status)
写到一个AJAX操做里很正常,可是很差看,并且不利于代码复用。网络
有没有更好的写法?好比写成这样:app
var ajax = ajaxGet('http://...'); ajax.ifSuccess(success) .ifFail(fail);
这种链式写法的好处在于,先统一执行AJAX逻辑,不关心如何处理结果,而后,根据结果是成功仍是失败,在未来的某个时候调用success
函数或fail
函数。dom
古人云:“君子一言既出;驷马难追”,这种“承诺未来会执行”的对象在JavaScript中称为Promise对象。异步
Promise有各类开源实现,在ES6中被统一规范,由浏览器直接支持。先测试一下你的浏览器是否支持Promi'use strict';
function test(resolve, reject) { var timeOut = Math.random() * 2; log('set timeout to: ' + timeOut + ' seconds.'); setTimeout(function () { if (timeOut < 1) { log('call resolve()...'); resolve('200 OK'); } else { log('call reject()...'); reject('timeout in ' + timeOut + ' seconds.'); } }, timeOut * 1000); }
这个test()
函数有两个参数,这两个参数都是函数,若是执行成功,咱们将调用resolve('200 OK')
,若是执行失败,咱们将调用reject('timeout in ' + timeOut + ' seconds.')
。能够看出,test()
函数只关心自身的逻辑,并不关心具体的resolve
和reject
将如何处理结果。
有了执行函数,咱们就能够用一个Promise对象来执行它,并在未来某个时刻得到成功或失败的结果:
var p1 = new Promise(test); var p2 = p1.then(function (result) { console.log('成功:' + result); }); var p3 = p2.catch(function (reason) { console.log('失败:' + reason); });
变量p1
是一个Promise对象,它负责执行test
函数。因为test
函数在内部是异步执行的,当test
函数执行成功时,咱们告诉Promise对象:
// 若是成功,执行这个函数: p1.then(function (result) { console.log('成功:' + result); });
当test
函数执行失败时,咱们告诉Promise对象:
p2.catch(function (reason) { console.log('失败:' + reason); });
Promise对象能够串联起来,因此上述代码能够简化为:
new Promise(test).then(function (result) { console.log('成功:' + result); }).catch(function (reason) { console.log('失败:' + reason); });
实际测试一下,看看Promise是如何异步执行的'use strict';
可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:
Promise还能够作更多的事情,好比,有若干个异步任务,须要先作任务1,若是成功后再作任务2,任何任务失败则再也不继续并执行错误处理函数。
要串行执行这样的异步任务,不用Promise须要写一层一层的嵌套代码。有了Promise,咱们只须要简单地写:
job1.then(job2).then(job3).catch(handleError);
其中,job1
、job2
和job3
都是Promise对象。
下面的例子演示了如何串行执行一系列须要异步计算得到结果的任务:'use strict';
setTimeout
能够当作一个模拟网络等异步执行的函数。如今,咱们把上一节的AJAX异步执行函数转换为Promise对象,看看用Promise如何简化异步处理:
除了串行执行若干异步任务外,Promise还能够并行执行异步任务。
试想一个页面聊天系统,咱们须要从两个不一样的URL分别得到用户的我的信息和好友列表,这两个任务是能够并行执行的,用Promise.all()
实现以下:
var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 500, 'P1'); }); var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 600, 'P2'); }); // 同时执行p1和p2,并在它们都完成后执行then: Promise.all([p1, p2]).then(function (results) { console.log(results); // 得到一个Array: ['P1', 'P2'] });
有些时候,多个异步任务是为了容错。好比,同时向两个URL读取用户的我的信息,只须要得到先返回的结果便可。这种状况下,用Promise.race()
实现:
var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 500, 'P1'); }); var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 600, 'P2'); }); Promise.race([p1, p2]).then(function (result) { console.log(result); // 'P1' });
因为p1
执行较快,Promise的then()
将得到结果'P1'
。p2
仍在继续执行,但执行结果将被丢弃。
若是咱们组合使用Promise,就能够把不少异步任务以并行和串行的方式组合起来执行。