Promise核心概述
Promise是js异步编程的一种解决方案,作为容器封装一些逻辑(通常是异步操作),并维护内部执行状态;状态主要是两种,初始状态为pending,操作结束后可改为fulfilled(resolved)或rejected;==then为Promise原型上的方法,通过传入参数函数(可以提供两个,分别对应两种状态)作为当前promise状态变化后的回调并返回一个新的Promise实例实现链式调用==,解决了callback方式的“回调地狱”问题;Promise.prototype.catch和Promise.prototype.finally实际上为Promise.prototype.then的特殊情况,本文不做赘述;
Promise基本使用
const promise = new Promise((resolve, reject) => {
...
resolve();
}).then(() => {
...
}).then(() => {
...
});
promise.then(() => {
...
});
先看一段熟悉的代码,尝试分析一下这样一段“简单”的代码到底干了什么事情,执行顺序是什么;由于Promise实现很多都使用了native代码,我们“曲线救国”,用polyfill来做一个分析;
下面是promise-polyfill这个库的github地址,代码少而精,值得一读;
promise-polyfill核心源码分析
为了方便抓住重点,下文的源码分析中我会直接省略掉非核心代码;
首先是Promise构造器,也就是我们通过new Promise()调用的方法;
/**
* @function Promise构造器
* @param fn 用户代码传给Promise构造器的参数函数
*/
function Promise(fn) {
// 在当前实例中保存执行状态,初始为pending
this._state = 0;
// 用户代码通过resolve(或reject)传入的参数
this._value = undefined;
// 当前Promise注册的回调列表
// 每个item实际上是callback + 提供该callback的then中创建的新Promise实例封装成的一个对象
this._deferreds = [];
// 调用doResolve,参数为构造器的参数函数及this,即当前promise实例
doResolve(fn, this);
}
可以看到Promise实例中会保存当前Promise内部的执行状态、内部操作结束后用户代码通过resolve或reject传入的参数、回调列表,其中回调列表的每一项其实是==callback和提供该callback的then中创建的新Promise实例封装成的一个对象==,理解这个很关键,不过我们先继续,一会再回过头来看这个对象;接着构造器中==同步==调用了doResolve,调用后执行结束返回promise实例;接下来看下doResolve实现;
/**
* @function 执行用户代码传给Promise构造器的参数函数,注入resolve、reject
* @param fn 用户代码传给Promise构造器的参数函数
* @param self 当前promise实例
*/
function doResolve(fn, self) {
try {
// 同步调用fn并注入两个函数作为参数
// 注意,这两个函数最终是提供给用户程序来调用的
fn(
function(value) {
resolve(self, value);
},
function(reason) {
reject(self, reason);
}
);
}
// fn异常时调用reject
catch (ex) {
reject(self, ex);
}
}
==doResolve主要作用就是执行用户代码给Promise构造器传入的参数函数==,并注入由Pormise内部提供的resolve和reject函数;接下来看看resolve和reject的实现;
/**
* @function 改变当前Promise实例的状态,保存用户传入的参数,并尝试执行回调
* @param self 当前promise实例
* @param newValue 用户代码传给resolve的参数
*/
function resolve(self, newValue) {
try {
// 内部状态改为resolved
self._state = 1;
// 保存用户代码传给resolve的参数
self._value = newValue;
finale(self);
} catch (e) {
reject(self, e);
}
}
function reject(self, newValue) {
// 内部状态改为rejected
self._state = 2;
self._value = newValue;
finale(self);
}
可见当用户代码中调用resolve后,当前Promise实例的执行状态会发生改变,并将用户代码传入的参数保存下来,并调用finale;所以==resolve和reject的主要作用就是改变当前promise的状态并尝试调用回调==;接下来看看finale的实现;
/**
* @function 通过遍历尝试执行当前Promise实例中保存的所有回调
* @param self 当前promise实例
*/
function finale(self) {
// 遍历当前Promise实例中保存的回调列表
for (var i = 0, len = self._deferreds.length; i < len; i++) {
// 调用handle方法执行callback
handle(self, self._deferreds[i]);
}
// 全部callback执行完成后列表置空
self._deferreds = null;
}
==finale的主要作用就是调用当前Promise实例中所有注册的callback==,那这些callback是怎么注册的呢?其实就是通过Promise.prototype.then;不过我们先看下handle方法是如何执行这些callback的;
/**
* @param self 当前promise实例
* @param deferred callback + 提供该callback的then中创建的新Promise实例封装成的一个对象
*/
function handle(self, deferred) {
// promise内部状态还未改变,即还是pending
if (self._state === 0) {
// 保存该回调并返回
self._deferreds.push(deferred);
return;
}
// 利用Promise上的静态方法_immediateFn来执行callback
Promise._immediateFn(function() {
// 根据promise内部状态(即resolved或rejected)执行对应callback
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
// 执行回调,参数为用户代码通过resolve或reject传入的值
var ret = cb(self._value);
// 再次调用resolve
// 传入deferred.promise(即提供该callback的then中创建的新Promise实例)和该callback的返回值
resolve(deferred.promise, ret);
});
}
Promise._immediateFn其实就是用setTimeout 0,即用宏任务模拟真实Promise实现中产生的微任务,不做赘述;可以看到handle方法做了几件事情,若当前Promise状态还在pending,则==将callback和提供该callback的then创建的promise封装成一个对象保存==到列表中,这个过程可以理解为==pending状态下promise的callback注册==;若当前Promise状态已改变,则根据状态==起一个异步任务,在异步任务中执行对应的callback==,polyfill中是宏任务,而真实的promise是微任务;另外一个重要的事情是在这个异步任务中执行callback后,再次调用resolve并传入deferred.promise,即提供这个微任务中执行的callback的then创建的promise,==利用resolve来改变这个promise的状态并尝试调用注册在这个promise中的callback==;
最后我们看下Promise.prototype.then的实现;
/**
* @param onFulfilled 当前promise状态变为resolved后的回调
* @param onRejected 当前promise状态变为rejected后的回调
*/
Promise.prototype.then = function(onFulfilled, onRejected) {
// 创建一个新的Promise,参数为一个空函数
var promise = new this.constructor(() => {});
// 调用handle尝试执行当前promise的回调
handle(this, new Handler(onFulfilled, onRejected, promise));
// 返回新创建的promise
return promise;
};
注意,由于then是Promise原型上的方法,所以函数体中this指向用户代码中.then前面的promise对象,这个对象可能是new Promise()返回的,也可能是then()返回的;可以看到,then方法中主要就做了两件事情,一个是创建一个新的Promise实例并返回;另一个是将then方法的参数,即两个callback和新创建的promise实例封装并传递给handle,尝试利用handle尝试调用this指向的promise的回调;回忆一下,==在handle中如果该promise状态还是pending,则会将回调注册起来,否则起一个异步任务执行==;
看一眼用来封装callback和提供该callback的then创建的promise的Handler构造;
/**
* @param onFulfilled resolved的回调
* @param onRejected rejected的回调
* @param promise promise实例
*/
function Handler(onFulfilled, onRejected, promise) {
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
小结
总结一下几个重要的点
- new Promise((resolve, reject) => {})中==同步调用==传入的fn函数并注入resolve、reject函数,Promise实例状态可通过resolve或reject发生变化,最后返回promise实例;
- Promise.prototype.then(resolveCallback, rejectCallback)中==同步==创建一个新的promise实例,并尝试调用then方法的参数,即callback函数;如果当前promise还在pending状态则将callback和then创建的promise保存在该promise中,可理解为注册回调,等待该promise状态变化后再起==异步任务==执行已注册的callback;如果当前promise已resolve或reject,则直接起一个==异步任务==,在该任务中根据状态执行对应callback,并在执行完callback后改变then创建的promise状态并调用该promise中注册的回调(同样以==异步任务==的方式);以此类推;
可见其实除了执行callback时会起一个异步任务(真实的Promise实现中是微任务,polifill中是宏任务),其他代码都是同步执行;
实例分析
来个稍微复杂一点的例子加深理解;
new Promise((resolve, reject) => {
console.log('outer promise');
resolve();
}).then(() => {
new Promise((resolve, reject) => {
console.log('inner promise');
resolve();
}).then(() => {
console.log('inner then1');
}).then(() => {
console.log('inner then2');
});
}).then(() => {
console.log('outer then1');
}).then(() => {
console.log('outer then2');
});
下面是输出结果;
outer promise
inner promise
inner then1
outer then1
inner then2
outer then2
我们来分析一下执行顺序;
- 首先开始同步执行外层Promise构造,构造器内部调用传入的参数函数,==输出outer promise==;接着用户代码调用resolve,外层promise状态变为resolved,构造器调用结束返回外层promise outerP1;
- 接着同步在outerP1上调用then方法,then内部检查outerP1状态,发现是resolved,于是起一个异步任务task1并同步返回一个新的promise实例outerP2(初始状态为pending),在task1中做两件事情,一是调用当前then中提供的callback,二是改变outerP1的状态并调用outerP1中注册的callback;
- 继续同步执行外层第二个then,由于outerP2状态还是pending,则将自己创建的promise outerP3和对应的callback都注册到outerP2中保存起来;
- 继续同步执行到外层第二个then,同样由于outerP3状态还是pending则将自己创建的promise outerP4和对应的callback都注册到outerP3中保存起来;
- ==第一轮同步代码执行结束==;
- 接下来开始执行刚才放进事件循环的异步任务task1,同步调用内层的Promise构造,构造器内部调用传入的参数函数,==输出inner promise==;接着用户代码调用了resolve,内层promise状态变为resolved,构造器调用结束,返回内层promise innerP1;
- 接着同步在innerP1上调用then方法,then内部检查当前promise状态,发现是resolved,于是起一个异步任务task2调用callback并同步返回一个新的promise实例innerP2;
- 继续同步执行内层第二个then,由于innerP2状态还是pending则注册自己和callback到innerP2,返回innerP3;
- 此时异步任务task1中的callback执行结束,改变outerP2的状态为resolved并起一个异步任务task3调用其内部保存的callback,即输出outer then1;
- 此时事件循环中还有task2和task3,按顺序先执行执行task2,==输出inner then1==,同时使innerP2状态变为resolved并起一个异步任务task4,在task4中调用innerP2中注册的callback,即输出inner then2;
- 继续执行task3,==输出outer then1==,并使outerP3状态变为resolved,起一个异步任务task5调用对应callback,即输出outer then2;
- 此时事件循环中还有task4和task5,先执行执行task4,==输出inner then2==,然后将innerP3状态改为resolved,由于innerP3后续没有调用then,即没有注册callback,则task4执行结束;
- 接着调用事件循环中最后一个任务task5,==输出outer then2==;
- 至此,所有代码执行完毕;
最后整理一下刚才的代码,贴上一些注释方便大家理解;
// 同步执行,返回outerP1
new Promise((resolve, reject) => {
console.log('outer promise');
// outerP1状态resolved
resolve();
})
// 同步执行,起异步任务task1,返回outerP2
.then(
// task1
() => {
// 同步执行,返回innerP1
new Promise((resolve, reject) => {
console.log('inner promise');
// innerP1状态resolved
resolve();
})
// 同步执行,起异步任务task2,返回innerP2
.then(
// task2
() => {
console.log('inner then1');
// task2执行结束后使innerP2状态变为resolved并起异步任务task4
})
// 同步执行,注册回调到innerP2,返回innerP3
.then(
// task4
() => {
console.log('inner then2');
});
// task1执行结束后使outerP2状态变为resolved并起异步任务task3
})
// 同步执行,注册回调到outerP2,返回outerP3
.then(
// task3
() => {
console.log('outer then1');
// task3执行结束后使outerP3状态变为resolved并起异步任务task5
})
// 同步执行,注册回调到outerP3,返回outerP4
.then(
// task5
() => {
console.log('outer then2');
});