在javascript中处理异步编程的主要方式是订阅事件,执行回调函数,比如我们常用的ajax
let xhq = new XMLHttpRequest(); xhq.open('GET', '/index'); xhq.onreadystatechange = function () { let {readyState, status} = xhq; if (readyState == 4 && status == 200) { let {responseText} = xhq; console.log(responseText); } } xhq.send()
我们的匿名回调函数订阅了onreadystatechange 事件,每当readyState变化时都会触发,所以我们监听到了readyState =4(响应体已经完全接受完毕)。
如果我们需要这个响应体中的数据去请求另外的服务时代码时就会写出这样的代码
let xhq = new XMLHttpRequest(); xhq.open('GET', '/index'); xhq.onreadystatechange = function () { let {readyState, status} = xhq; if (readyState == 4 && status == 200) { let {responseText} = xhq; //下面的请求依赖responseText; let xhq_ = new XMLHttpRequest(); xhq_.open('POST', '/other'); xhq_.onreadystatechange = function() { let {readyState, status} = xhq_; if (readyState == 4 && status == 200) { let {responseText} = xhq_; } } xhq_.send(responseText); } } xhq.send()
这样的回调就容易形成回调地狱,代码的可读性极低。
Promise就是为了解决异步回调问题,中文直译是承诺的意思,下面引自廖雪峰对promise的介绍
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,
Promise
对象有以下两个特点。
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,下面的代码是用promise对ajax请求的包装。
function promisefyAjax (type, url, data = {}) { return new Promise((resolve, reject) => { let xhq = new XMLHttpRequest(); xhq.open(type, url); xhq.onreadystatechange = function () { let {status, readyState} = xhq; if (status == 200) { if (readyState == 4 ) { resolve(xhq.responseText) } } else { reject('请求失败'); } } xhq.send(data); }); } promisefyAjax('GET', '/index') .then(data => promisefyAjax('POST', '/other', {data})) .catch(err => console.error(err))
Promise 对象有then方法可以传递两个回调函数, 第一个能够拿到异步执行后resolve()出来的结果, 第二个能够能到reject()出来的错误, 也可以省略第二个回调,而在promise对象的catch方法中捕获jeject结果。
可以看到 通过promise的包装,有依赖的两个异步请求,没有了回调地狱的影子,代码组织简洁,可读性高。
但是promise也有做不到的地方,比如我们在上传和下载时经常会监听onprogress 来时时的显示当前事件执行进度情况,这是无法用promise包装的,因为promise只有两种状态,要么完成态,要么失败态。
Promise 也有统一的API
Promise.all([ ])方法可以传递一组promise对象,最后的执行时间取决于执行时间最长的promise对象,如下
function fn (time) { return new Promise((resolve, reject) => { setTimeout(resolve, time) }) } let p1 = fn(5000); let p2 = fn(8000); let p3 = fn(3000); let timestamp = Date.now(); Promise.all([ p1, p2, p3 ]).then(arr => console.log(Date.now()-timestamp));
结果
执行结果时7999毫秒, 跟时间最长的promise相同
Promise.race([ ])则是取决去数组中执行时间最短的promise