一、Promise介绍
Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和强大。Promise有以下两个特点:
- 对象的状态不受外界影响。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。事件的特点是,如果错过了它,再去监听,是得不到结果的。
Promise缺点:
- 无法取消Promise,一旦新建就会立即执行,无法中途取消;
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;
- 当处于pending状态时,无法得知目前进展到哪一个阶段。
- Promise.prototype.finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。
Promise
.then(result => {...})
.catch(error => {...})
.finally(() => {...});
finally方法的回调函数不接受任何参数,不依赖与Promise的执行结果。例如,可以公仔服务器使用Promise处理请求,然后使用finally方法关掉服务器。
- Promise.all()
该方法用于多个Promise实例,包装成一个新的Promise实例。
const p = Primise([p1, p2, p3]);
Promise.all接收一个数组作为参数,p1 p2 p3都是Promise实例,如果不是,就会先调用Promise.resolve方法,将参数转为Promise实例,再进一步处理。
p的状态由p1 p2 p3决定:
(1)只有p1 p2 p3的状态都是fulfilled,p的状态才会变成fulfilled,此时p1 p2 p3的返回值组成一个数组,传递给p的回调函数;
(2)只要p1 p2 p3中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
- Promise.race()
该方法也是将多个Promise实例包装成一个新的Promise实例。
const p = Promise.race([p1, p2, p3]);
Promise.race与Promise.all一样,接收一个数组作为参数,p1 p2 p3都是Promise实例,如果不是,就会先调用Promise.resolve方法,将参数转为Promise实例再做进一步处理。
p的状态有pa p2 p3中第一个改变的状态决定,只要其中一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。
下面是一个经典例子:
// 如果指定时间内没有获得结果,就将Promise的状态reject掉
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
-
Promise.resolve()与Promise.reject()
将对象转为Promise对象 -
注意一些只能同步执行的函数,比如foreach等
二、ES5实现Promise
- 构造函数
function Promise(executor){
var self = this;
self.status = 'pending';
self.data = undefined;
// resolve或者reject之后的回调函数集,因为在Promise结束之前可能有多个回调函数;
self.onResolvedCallback = [];
self.onRejectedCallback = [];
// 执行器
executor(resolve, reject);
}
- executor函数有两个参数:resolve与reject,这两个函数需要定义
function Promise(executor){
var self = this;
self.status = 'pending';
self.data = undefined;
// resolve或者reject之后的回调函数集,因为在Promise结束之前可能有多个回调函数;
self.onResolvedCallback = [];
self.onRejectedCallback = [];
function resolve(value){
// TODO
}
function reject(reason){
// TODO
}
// 执行executor可能会出错,用try...catch将错误信息reject出去
try{
executor(resolve, reject);
} catch(e){
reject(e);
}
}
由于resolve与reject两个函数在调用的时候,有其自己的this,但是resolve和reject执行的时候,this应该指向Promise内部。如果把resolve与reject定义在Promise 的外部,在使用的时候,需要调用bind函数将this指向Promise内部,但是bind函数会返回一个新的函数,还是相当于每个Promise对象都有属于自己的resolve与reject函数,与定义在函数内部没有区别。
3. resolve函数与reject函数
function Promise(executor){
var self = this;
...
function resolve(value){
if(self.status === 'pending'){
self.status = 'resolved';
self.data = value;
for(var i = 0; i < self.onResolvedCallback.length; i++){
self.onResolvedCallback[i](value);
}
}
}
function reject(reason){
if(self.status === 'pengding'){
self.status = 'rejected';
self.data = reason;
for(var i = 0; i > self.onResolvedCallback.length; i++){
self.onRejectedCallback[i](reason);
}
}
}
}
- then方法
then方法用来注册在这个Promise状态确定后的回调,then方法会返回一个Promise,才可以进行链式调用。
// then函数接收两个参数:onResolved, onRejected,分别代表成功或者失败后的回调
Promise.prototype.then = function(onResolved, onRejected){
// 根据标准,then的参数如果不是function,需要忽略它
var onResolved = typeof onResolved === 'function' ? onResolved : function(value){};
var onRejected = typeof onRejected === 'function' ? onRejected : function(reason){};
var self = this;
var promise2;
if(self.status === 'resovled'){
// 若promise1的状态已经确定并且是resolved,直接调用onResolved,但是有可能throw,所以包裹在try...catch中
return promise2 = new Promise(function(resolve, reject){
try{
var x = onResolved(self.data);
// 若onResolved返回的是一个Promise,则直接将它的结果返回
if(x instanceof Promise){
x.then(self.data);
}
// 否则直接将它的返回值作为promise2的结果
resolve(x);
} catch(e){
reject(e);
}
})
}
if(self.status === 'reject'){
return promise2 = new Promise(function(resolve, reject){
try{
var x = onRejected(self.data);
if(x instanceof Promise){
x.then(resolve, reject);
}
} catch(e){
reject(e);
}
})
}
if(slef.status === 'pending'){
// 如果当前的Promise还处于pending状态,我们并不能确定调用onResolved还是onRejected,
// 只能等到Promise的状态确定后,才能确实如何处理。
// 所以我们需要把我们的**两种情况**的处理逻辑做为callback放入promise1(此处即this/self)的回调数组里
// 逻辑本身跟第一个if块内的几乎一致,此处不做过多解释
return promise2 = new Promise(function(resolve, reject) {
self.onResolvedCallback.push(function(value) {
try {
var x = onResolved(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
});
})
}
}