第14章 Promise对象
概要
Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理更强大。
在学习本章前有必要去了解一下js的Event Loop机制。
14.1 Promise的含义
Promise对象有两个特点:
1.对象的状态不受外界影响。Promise对象代表一个异步操作,有3种状态:Pending(进行中)、Fulfilled(已成功)和Rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个状态。
2.一旦状态改变就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变只有两种可能:从Pending变为Fulfilled和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变,而是一直保持这个结果。
Promise对象的出现,避免了层层嵌套的回调函数,从可读性角度来看,我们可以认为有了Promise对象,就可以将异步操作以同步操作的流程表达出来。
Promise也有缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误不会反应到外部。再者,当处于Pending状态时,无法得知目前进展到哪一个阶段。
14.2 基本语法
function PromiseTestFunc(ms) {
let instance = new Promise((resolve,reject) =>
{
setTimeout(resolve,ms,'done');
}
);
return instance;
}
PromiseTestFunc(10000).then((value)=>
{console.log(value);}
);
上述代码表明一个当状态由Pending转为Resolve时,我们将done字符串传入回调函数的过程。即Promise实例的状态发生转变时,就会触发then方法绑定相应的回调函数(此处为resolve回调函数,then的第二个参数就是在转变为Rejected状态所使用的。)
要着重讲一下以下这种情况,当resolve函数中传入的参数是Promise对象。
先看代码
var p1 = new Promise((resolve, reject) => {
console.log('p1 execute.')
setTimeout(() => {
reject(new Error('fail'));
},3000);
});
var p2 = new Promise((resolve, reject) => {
console.log('p2 execute.')
setTimeout(() => {
resolve(p1);
}, 1000);
});
p2
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error);
})
// Error: fail
从代码中可以知道p2的状态虽然从Pending变为了Resolve,但是由于Resolve函数中返回的参数是一个任出于Pending状态的Promise对象p1,所以要先等待p1的状态完成变换再执行回调函数;若p1的状态已经变换完成(无论是resolve还是reject)p2的回调函数都会立刻执行。
14.3 Promise.prototype.then()
Promise实例具有的then方法,其作用是为Promise实例添加回调函数。
/**
* Represents the completion of an asynchronous operation
*/
interface Promise<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}
在定义中可以看到then方法的返回结果依然是一个Promise实例,因此在then方法可以继续调用then方法。这种链式调用,每个then方法中的回调函数都是处理上一个Promise实例的传入参数。
14.4 Promise.prototype.catch()
Promise.prototype.catch方法是.then(null,rejection)的别名,用于指定发生错误时的回调函数。
Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获。除非有特殊需求,一般不要在then方法中写Rejected状态的回调函数,而应该总是使用catch方法。
特别要强调的是类似如下写法,哪怕出现了错误但由于Promise实例的状态已经变换,异常会成为未捕捉的错误。
var p = new Promise((resolve,reject) => {
resolve('done');
setTimeout(() => {
throw new Error('error occurred');
}, 0);
});
在前端也可以通过监听异常来捕捉这些抛到最外层没人要的异常。
/**
* 用于捕获未处理的Promise错误
*/
window.addEventListener('unhandledrejection',event => {
console.log('unhandledrejection:' + event.reason);
});
/**
* js中最常见的error事件的事件处理程序
*/
window.onerror = function(message, source, lineno, colno, error) {
console.log(error);
}
14.5 Promise.all()
将多个Promise实例包装成一个Promise。语义类似于c#中的Task.WaitAll方法,等待一组Promise实例都完成状态变换再执行回调函数。
14.6 Promise.race()
该方法同样是将多个Promise实例包装成一个Promise。