Promise
对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口。它起到代理作用(proxy
),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。Promise
可以让异步操作写起来就像在写同步操作的流程,而不必一层层地嵌套回调函数。
let p1 = new Promise(f1);
p1.then(f2)
/* 回调函数f1作为参数
* 返回的p1就是一个Promise实例
* 执行完f1 就会执行f2
// Promise 的写法
(new Promise(step1))
.then(step2)
.then(step3)
.then(step4);
Promise对象的状态
Promise 实例具有三种状态:
- 异步操作未完成(pending)
- 异步操作成功(fulfilled)
- 异步操作失败(rejected)
上面三种状态里面,fulfilled
和rejected
合在一起称为resolved
(已定型)
Promise 的最终结果只有两种:
- 异步操作成功,Promise 实例传回一个值(value),状态变为
fulfilled
- 异步操作失败,Promise 实例抛出一个错误(error),状态变为
rejected
Promise构造函数
JavaScript 提供原生的Promise
构造函数,用来生成 Promise 实例。
let promise = new Promise(function (resolve, reject) {
/* resolve和reject是两个函数,由JS引擎提供,不用自己实现。
* resolve函数的作用是,将Promise实例的状态从“未完成”变为“成功”(即从pending变为fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
* reject函数的作用是,将Promise实例的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
*/
if (/* 异步操作成功 */){
resolve(value);
} else { /* 异步操作失败 */
reject(new Error());
}
});
Promise.prototype.then()
Promise 实例的then
方法,用来添加回调函数。
then
方法可以接受两个回调函数,第一个是异步操作成功时(变为fulfilled
状态)的回调函数,第二个是异步操作失败(变为rejected
)时的回调函数(该参数可以省略)。一旦状态改变,就调用相应的回调函数。
- `then`方法可以链式使用:
let p1 = new Promise((resolve, reject) => {
//...
})
p1
.then(step1)
.then(step2)
.then(step3)
.then(
console.log,
console.error
);
// p1后面有四个then,意味依次有四个回调函数。只要前一步的状态变为fulfilled,就会依次执行紧跟在后面的回调函数。
/* 最后一个then方法,回调函数是console.log和console.error,用法上有一点重要的区别。console.log只显示step3的返回值,而console.error可以显示p1、step1、step2、step3之中任意一个发生的错误。
如果step1的状态变为rejected,那么step2和step3都不会执行了(因为它们是resolved的回调函数)。
*/
实例:图片加载
var preloadImage = function (path) {
return new Promise(function (resolve, reject) {
var image = new Image(); //image是一个图片对象的实例
image.onload = resolve; //onload属性在图片加载成功后调用
image.onerror = reject; //onerror属性在加载失败调用
image.src = path;
});
};
preloadImage('https://example.com/my.jpg')
.then(function (e) { document.body.append(e.target) })
.then(function () { console.log('加载成功') })
//图片加载成功以后,onload属性会返回一个事件对象,因此第一个then()方法的回调函数会接收到这个事件对象。该对象的target属性就是图片加载后生成的 DOM 节点。
优缺点
Promise
的优点在于, 让回调函数变成了规范的链式写法,程序流程可以看得很清楚。它有一整套接口,可以实现许多强大的功能,比如同时执行多个异步操作,等到它们的状态都改变以后,再执行一个回调函数;再比如,为多个回调函数中抛出的错误,统一指定处理方法等等。
Promise
的缺点是,编写的难度比传统写法高,而且阅读代码也不是一眼可以看懂。你只会看到一堆then
,必须自己在then
的回调函数里面理清逻辑。
微任务
Promise 的回调函数属于异步任务,会在同步任务之后执行。
new Promise(function (resolve, reject) {
resolve(1);
}).then(console.log);
console.log(2);
// 2
// 1
上面代码会先输出2,再输出1。因为console.log(2)
是同步任务,而then
的回调函数属于异步任务,一定晚于同步任务执行。
但是,Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。
setTimeout(function() {
console.log(1);
}, 0);
new Promise(function (resolve, reject) {
resolve(2);
}).then(console.log);
console.log(3);
// 3
// 2
// 1
// 这说明`then`的回调函数的执行时间,早于`setTimeout(fn, 0)`。因为`then`是本轮事件循环执行,`setTimeout(fn, 0)`在下一轮事件循环开始时执行。