前言
此次阅读的 promise 实现是 ES6-Promise ,是 promise 的众多实现中较为完善的一个例子,从入口文件 lib/es6-promise/promise.js 开始阅读,可以看到 Promise 定义有如下的静态方法
Promise.all
Promise.race
Promise.resolve
Promise.reject
以及挂载在 Promise.protoype 上的方法
Promise.prototype.then
Promise.prototype.catch
Promise.prototype.finally
接下来会对其核心代码进行解读。
Promise的三种状态
Promise规范中定义了promise的三种状态,而且promise的状态更新只能是以下两种情况,一旦状态发生改变之后就不能再修改:
- PENDING -> FULFILLED
- PENDING -> REJECTED
ES6-Promise 内部中的定义如下:
// void 运算符 对表达式进行求值,结果都返回 undefined
// 可以防止undefined被重写,如:const undefined = xxx
const PENDING = void 0; // undefined
const FULFILLED = 1;
const REJECTED = 2;
复制代码
Promise的使用例子
我们来看一个Promise的使用例子,传入一个resolver函数,初始化一个promise实例。resolver函数会被立即执行,并且可以使用promise内部传入的两个参数 resolve 和 reject ,resolve 和 reject都是函数,接收一个参数, value 或者 reason,在resolver函数内部调用 resolve 或者 reject ,都会更新promise的状态 。
具体调用例子如:
const promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */){
// 更新状态为 fulfilled 并缓存 value
resolve(value);
} else {
// 更新状态为 rejected 并缓存 reason
reject(error);
}
});
复制代码
为了响应promise不同的状态(fulfilled 或 rejected),可以通过实例的 then 方法绑定回调函数:
promise.then(
// resolver 中 resolve 的 value
value => { /* pending -> fulfilled 时调用 */
},
// resolver 中 reject 的 resaon
reason => { /* pending -> rejected 时调用 */
}
)
复制代码
这样就完成了一个promise的基本使用。
Promise的构造函数
由上面的基本调用开始,先看看promise的构造方法:
class Promise {
constructor(resolver) {
// export const PROMISE_ID = Math.random().toString(36).substring(2);
// nextId() 返回一个自增的闭包变量
// Promise实例的唯一标识
this[PROMISE_ID] = nextId();
// 初始化变量
// _result 缓存 resolve 时的 value 或者 reject 时的 reason
// _state 缓存 promise 的 当前状态 初始化为 pending
this._result = this._state = undefined;
// 订阅序列
this._subscribers = [];
// noop = ()=>{} 内部定义的一个空函数
// 内部调用时: new Promise(noop) 不会经过下面的初始化过程
// 外部调用时: 对传入参数进行检查
if (noop !== resolver) {
// 条件1:resolver 必须要是一个函数
typeof resolver !== 'function' && needsResolver();
// 条件2: 必须通过 new Promise() 初始化实例
this instanceof Promise ? initializePromise(this, resolver) : needsNew();
// 符合以上条件的进行初始化
// initializePromise() 函数
}
}
复制代码
可以看到promise的构造方法完成了以下的工作:
-
初始化变量
[PROMISE_ID] : 该实例独一无二的ID
result: 缓存value或者reason
subscribers: 订阅promise状态变化的序列(后面会讲到)
-
对入参 resolver 进行判断
resolver === noop noop是内部定义的一个空函数,此时 promise 会初始化结束。
resolver !== noop resolver 必须是函数,同时必须是通过 new Promise() 初始化实例。满足以上两个条件的,就会调用 initializePromise ,对实例进一步初始化,其代码如下:
function initializePromise(promise, resolver) {
try {
// 立即执行 resolver函数
// 传入包装好的参数
resolver(function resolvePromise(value) {
resolve(promise, value);
}, function rejectPromise(reason) {
reject(promise, reason);
});
} catch (e) {
// 代码发生异常
// 则直接更新 promise 的状态为 rejected
reject(promise, e);
}
}
复制代码
可以看到,resolver函数被立即执行了,而且在 resolver 函数中调用的 resolve 和 reject 就是 resolvePromise 和 rejectPromise 函数,这也是promise提供给resolver的两个入参,它们包装了内部实现的 resolve 和 reject 函数。
再来看看内部的 resolve和 reject 是如何对promise状态进行更新的。
内部的 reject 函数
reject函数较为简单:
/**
* pending => rejected
*/
function reject(promise, reason) {
// 状态一旦改变就不可逆转
if (promise._state !== PENDING) {
return;
}
// 缓存处理结果 更新promise状态
promise._state = REJECTED;
promise._result = reason;
// 注:asap 函数暂不详细解释 后面源码中多次用到
// asap(func,param) 可以理解为立即执行 func(param)
// promise 的状态发生了改变 通知订阅者
asap(publishRejection, promise);
}
复制代码
promise的状态更新为rejected后,会通过publishRejection向订阅序列发出通知,publishRejection函数代码如下,具体的publish函数封装了如何向订阅序列发出通知的逻辑,后续会说到:
function publishRejection(promise) {
// ???
// onError 好像一直都是null啊
if (promise._onerror) {
promise._onerror(promise._result);
}
// promise的状态发生了改变
// 向订阅序列发出通知
publish(promise);
}
复制代码
内部的 resolve 函数
resolve 则会对 value 的类型进行判断,情况有以下:
- value是实例本身,更新promise的状态为 rejected,不允许resolve本身
- value是对象或者函数,调用 handleMaybeThenable()方法进行特殊处理
- 其他情况,调用 fulfill 方法更新状态
具体代码逻辑如下:
function resolve(promise, value) {
if (promise === value) {
// 1. 不允许 value 是 promise实例本身
// 更新为 rejected 状态
reject(promise, selfFulfillment());
} else if (objectOrFunction(value)) {
// 2. value是对象或者函数
handleMaybeThenable(promise, value, getThen(value));
} else {
// 3. 其他数据类型
fulfill(promise, value);
}
}
复制代码
先看看 fulfill 方法,方法不难理解,就是更新promise的状态为 fulfilled 同时向订阅序列发出通知。
/**
* pedding => fulfilled
*/
function fulfill(promise, value) {
// promise状态不可逆
if (promise._state !== PENDING) {
return;
}
// 缓存处理结果 修改promise状态
promise._result = value;
promise._state = FULFILLED;
// promise的状态发生了改变
// 向订阅序列发出通知
if (promise._subscribers.length !== 0) {
asap(publish, promise);
}
}
复制代码
再来看看当 resolve 函数传入的value是对象或者函数,调用特殊处理的 handleMaybeThenable(promise,value,getThen(value))方法。其中getThen方法接收一个参数promise,用于尝试获取promise的then属性,会捕获错误,返回包装的错误对象。函数内部又对value进行了进一步划分。
handleMaybeThenable的具体代码如下:
function handleMaybeThenable(promise, maybeThenable, then) {
if (
maybeThenable.constructor === promise.constructor &&
then === originalThen &&
maybeThenable.constructor.resolve === originalResolve) {
// 1. resolve的值是一个 promise 实例
// 即通过 new Promise() 新建的实例
handleOwnThenable(promise, maybeThenable);
} else {
// 2. 获取 value 的 then 属性时发生错误
// 更新 promise 的 状态为 rejected
if (then === TRY_CATCH_ERROR) {
reject(promise, TRY_CATCH_ERROR.error);
TRY_CATCH_ERROR.error = null;
} else if (then === undefined) {
// 3. 虽然 resolve的值是 函数或者对象 但不存在 then 属性
// 按照普通数据类型进行处理
fulfill(promise, maybeThenable);
} else if (isFunction(then)) {
// 4. resolve的值是一个 thenable 对象, 具有then属性,并且为函数
handleForeignThenable(promise, maybeThenable, then);
} else {
// 5. then 是其他数据类型 直接进行处理
fulfill(promise, maybeThenable);
}
}
}
复制代码
重新总结一下 resolve 的值分为以下情况:
- value为 promise 实例,调用handleOwnThenable方法进行处理
- value为 thenable 实例(一个具有then方法的对象),调用handleForeThenable方法进行处理
- 其他情况,直接调用 fulfill 方法处理值
第一种情况中,handleOwnThenable 具体代码如下:
/**
* resolve 的值 value 是一个 promise 实例
* @param promise 当前的promise
* @param thenable value
*/
function handleOwnThenable(promise, thenable) {
// 检查 thenable 状态
if (thenable._state === FULFILLED) {
// FULFILLED 状态
// 使用 thenable 内部缓存的处理结果
// 作为 value 调用 fulfill 更新状态
fulfill(promise, thenable._result);
} else if (thenable._state === REJECTED) {
// REJECTED 状态
// 使用 thenable 内部缓存的处理结果
// 作为 reason 调用 reject 更新状态
reject(promise, thenable._result);
} else {
// pending 状态, 对 thenable 增加监听
// 当 thenable 的状态改变时
// 用其结果缓存 更新 promise 的状态
subscribe(thenable, undefined,
value => resolve(promise, value),
reason => reject(promise, reason))
}
}
复制代码
也就是说,当resolve的value是一个promise时,原封不动地返回这个promise。
第二种情况,当为一个具有then属性并且then为函数的对象时:
// let thenable = {
// then: function(resolve, reject) {
// resolve(42);
// }
// };
function handleForeignThenable(promise, thenable, then) {
asap(promise => {
// 用一个 sealed 保证只能调用一次
var sealed = false;
// tryThen 方法
// 1. 以 thenable 作为执行上下文
// 2. 传入 (value=>{} , reason=>{}) 参数
// 3. 执行 then 函数
//
var error = tryThen(then, thenable, value => {
if (sealed) {
return;
}
sealed = true;
if (thenable !== value) {
// 如果 resolve 的 value 不是原来的thenale对象
// 进一步对value进行处理(有可能又是一个promise实例或者thenable对象,或者其他情况...)
resolve(promise, value);
} else {
// 如果 resolve 的 value 还是原来的thenale对象
// 则直接 fulfill 这个对象
// 不再进一步通过 resolve 避免死循环
fulfill(promise, value);
}
}, reason => {
if (sealed) {
return;
}
sealed = true;
// then 方法 reject(‘xx')
reject(promise, reason);
}, 'Settle: ' + (promise._label || ' unknown promise'));
// if error
// rejected 状态
if (!sealed && error) {
sealed = true;
reject(promise, error);
}
}, promise);
}
复制代码
也就是说,当resolve的值是一个thenable对象时,会执行其then方法,根据then方法中调用入参resolve或者reject时传入的value或reason来对promise的状态进行更新。
subscribe 订阅机制
之前有提到promise的订阅,在Promise初始化的时候,声明了一个内部变量,用来存储对promise的订阅序列。
// 订阅序列
this._subscribers = [];
复制代码
然后可以调用 subscribe 函数注册监听:
/**
* @param {promise} parent 被订阅状态变化的Promise
* @param {promise} child 订阅状态变化的Promise(可为undefined)
* @param {function} onFulfillment 状态变化为 fulfilled 的订阅函数
* @param {function} onRejection 状态变化为 rejected 的订阅函数
*/
function subscribe(parent, child, onFulfillment, onRejection) {
let { _subscribers } = parent;
let { length } = _subscribers;
parent._onerror = null;
// 每调用一次subscribe函数 订阅序列会增加三个元素
_subscribers[length] = child;
_subscribers[length + FULFILLED] = onFulfillment;
_subscribers[length + REJECTED] = onRejection;
// 有可能是在 parent 状态更新之后增加的订阅
// 此时的订阅序列被清空 状态不再发生变化 则直接发出通知
if (length === 0 && parent._state) {
asap(publish, parent);
}
}
复制代码
可以看到,对一个promise订阅其状态更新时,会在其订阅序列增加三个参数,子promise(可为空),状态变为fulfilled时的回调,变为rejected时的回调。当一个promise状态更新之后,就会触发publish函数,发布修改,publish的具体代码逻辑如下:
/**
* Promise状态更新 发出通知
* @param {Promise} promise
*/
function publish(promise) {
// 所有订阅序列
let subscribers = promise._subscribers;
// promise的状态
let settled = promise._state;
// 订阅序列为空
if (subscribers.length === 0) {
return;
}
/**
* child: 订阅状态变化的promise
* callback: 回调函数
* detail: value或者reason缓存
*/
let child, callback,
detail = promise._result;
// 每三个元素为一个订阅单位
// 格式如: child + fufilled + rejected
for (let i = 0; i < subscribers.length; i += 3) {
// child === [object Promise]
child = subscribers[i];
// 根据 promsie 的状态获取相应的回调函数
callback = subscribers[i + settled];
// 情况1: 存在promise实例订阅
if (child) {
invokeCallback(settled, child, callback, detail);
} else {
// 情况2: 触发响应的回调函数
callback(detail);
}
}
// 清空监听数组
promise._subscribers.length = 0;
}
复制代码
每三个元素为一个单位,publish函数会依次判断订阅者:
- 订阅存在子promise,存在则再调用 invokeCallback 函数
- 订阅不存在,直接传入结果缓存value或者reason 调用相应的回调函数
第一种情况下:
/**
* settled : 被监听promise的状态
* promise : 订阅监听的promise
* callback : fullfled | rejected
* detial : value | reason
*/
function invokeCallback(settled, promise, callback, detail) {
let hasCallback = isFunction(callback),
value, error, succeeded, failed;
if (hasCallback) {
// 尝试执行回调函数
// 发生错误时 则返回 TRY_CATCH_ERROR 对象
value = tryCatch(callback, detail);
// 执行cb函数时出错
if (value === TRY_CATCH_ERROR) {
// 获取error的值
failed = true;
error = value.error;
value.error = null;
} else {
// 设置状态
succeeded = true;
}
// 不允许回调函数返回了原有的promise实例
if (promise === value) {
reject(promise, cannotReturnOwn());
return;
}
} else {
// 不存在回调函数
value = detail;
succeeded = true;
}
// 更新 promise 的状态
if (promise._state !== PENDING) {
// noop
} else if (hasCallback && succeeded) { // 回调函数存在的情况
// value 是回调函数的返回值
resolve(promise, value);
} else if (failed) {
// error 是调用回调函数的错误时的原因
reject(promise, error);
} else if (settled === FULFILLED) { // 回调函数不存在的情况
// 直接使用父promise的value
fulfill(promise, value);
} else if (settled === REJECTED) {
reject(promise, value);
}
}
复制代码
总结一下,订阅一个promise 的状态变化,主要有两种情况:
- 一种是仅传入了回调函数,不存在子promise订阅,此时会promise的状态一旦变化,就会传入value或者reason,调用相应的回调函数。
- 另一种是订阅存在子promise,此时promise的状态一旦变化,又会分成回调函数有无两种情况, 有回调函数的话,传入value或者reason到相应的回调函数执行,回调函数的返回值作为结果缓存到子promise。 无回调函数的话,就直接使用value或者reason作为结果缓存。
Promise.prototype.then
方法传入两个函数参数,当promise的状态更新后,调用相应的回调函数,同时返回一个新的promise。
export default function then(onFulfillment, onRejection) {
// 当前的 promise
const parent = this;
// 新建一个 promise
// new pormise(()=>{})
// [!] 通过此方式创建的 promise 不会进行 initializePromise 方法初始化
// initializePromise 会执行传入的函数 空函数不需要执行 只需要初始化相关变量
const child = new this.constructor(noop);
// 仅初始化状态变量
// promise[PROMISE_ID] = id++;
// promise._state = undefined;
// promise._result = undefined;
// promise._subscribers = [];
if (child[PROMISE_ID] === undefined) {
makePromise(child);
}
// 获取当前 promise 的状态
const { _state } = parent;
// 当前的 promise 为 fullfied / rejected
// 直接处理
if (_state) {
// state = fullfied ===> onFulfillment
// state = rejected ===> onRejection
// 获取与状态对应的处理函数
const callback = arguments[_state - 1];
// 传入结果立即执行
// 更新新建promise的状态
asap(() => invokeCallback(_state, child, callback, parent._result));
} else {
// 当前的 promise 为 pending
// 将新建的 promise 绑定在 当前 promise 上
// 当前的 pormise 状态改变之后 同步新建的 promise 状态
subscribe(parent, child, onFulfillment, onRejection);
}
// 返回新建的 promise
return child;
}
复制代码
总结一下,首先 then 方法会返回一个新的promise,其状态和原来的 promise 同步,结果缓存则有以下的两种情况。
- 如果设置了回调函数(onFulfillment或onRejection),会将原有promise的value或者reason传入回调函数,回调函数的返回结果缓存到新的promise实例中。
- 如果没有设置回调函数,会将原有的promise的value或者reason直接缓存给新的promise实例。
也就是下面的两种例子:
// 1. 第一种情况
let promise = new Promise((resolve,reject)=>{
resolve('OK')
})
let child1 = promise.then((value)=>{
return value + 'child1'
},()=>{})
// 此时child1的结果缓存是 'OKchild1'
// 2. 第二种情况
let child2 = promise.then().then().then().then().then( value =>{
console.log(value) // 结果缓存还是最开始那个promise 也就是 'OK'
})
复制代码
Promise.protoype.catch
catch方法是then方法的语法糖,通常我们会这样设置回调:
let promise = new Promise((resolve,reject)=>{
if(/* 异步OK*/){
resolve('OK')
}else{
reject(new Error('something wrong'))
}
}).then(value=>{
// xxx
}).catch(err=>{
// xxx
})
// 但实际上等同于
promise.then( value=>{},err=>{})
复制代码
其代码也很简单:
/**
* Promise.prototype.catch
*/
catch (onRejection) {
return this.then(null, onRejection);
}
复制代码
Promise.prototype.finally
finally方法用于无论状态发生了什么改变(fulfilled或者rejected)都要执行的操作,来自ES2018的标准。 注意:代码中的Promise.resolve 可先看看下面关于此方法的解读。
/**
* Promise.prototype.finally
*/
finally(callback) {
// 当前 promise
let promise = this;
// Promise
let constructor = promise.constructor;
if (isFunction(callback)) {
// Promise.resolve 包裹成一个promise,再调用其 then 方法返回一个新的promise
return promise.then(
// pending -> fulfilled
value => constructor.resolve(callback()).then(() => value),
// pending -> rejected
reason => constructor.resolve(callback()).then(() => {
throw reason;
}));
}
// callback 不是函数
return promise.then(callback, callback);
}
复制代码
Promise.resolve
方法接收一个参数,返回一个Promise,可接受的输入情况有:
// 1. 传入的是一个 promise实例
// 直接返回这个实例
Promise.resolve(promise);
// 2. 传入的是一个 thenable实例
// 执行 thenable 的 then 方法
// 根据 方法内部 resolve 或者 reject 得到的 value 或者 resaon
// 设置为 promise 的状态和结果缓存
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
Promise.resolve(theanable);
// 3. 普通数据值 返回一个 fulfilled 状态的 promise
// 结果缓存为其 value
Promise.resolve(value);
复制代码
具体代码实现如下
export default function resolve(object) {
// Promise.resolve = Resolve;
// [ Promise ]
let Constructor = this;
// 1. 传入一个 promise 实例
// 则直接返回这个promise
if (object
&& typeof object === 'object'
&& object.constructor === Constructor) {
return object;
}
// 新建一个空的 promise
// 不会经过 initialPromise() 方法的初始化
let promise = new Constructor(noop);
// 2. 可能是 thenable 对象
// 或者是其他普通值
_resolve(promise, object);
// 返回 promise
return promise;
}
复制代码
Promise.reject
方法较为简单,接收一个参数reason,直接返回一个状态为rejected的promise实例。
export default function reject(reason) {
// Promise.reject
// Promise
let Constructor = this;
// new Promise(()=>{})
// 不会经过 initialPromise() 方法的初始化
let promise = new Constructor(noop);
// pending -> rejected
_reject(promise, reason);
// 返回promise
return promise;
}
复制代码
Promise.race
方法传入一个 数组,一般传入多个promise,包装成一个promise返回,这个promise的状态和传入的多个promise中最先更新状态那一个promise同步。
export default function race(entries) {
// Promise
let Constructor = this;
if (!isArray(entries)) {
// 不是数组 返回一个状态为rejected的Promise实例
return new Constructor((_, reject) => reject(new TypeError('You must pass an array to race.')));
} else {
// 返回一个新的 Promise 实例
return new Constructor((resolve, reject) => {
let length = entries.length;
for (let i = 0; i < length; i++) {
// Promise.resolve(entries[i]).then(resolve,reject)
// 遍历所有元素 用 Promise.resolve() 包装成 Promise实例
// 最先的一个 Promise 更新了状态后 都会同步外层 Promise
Constructor.resolve(entries[i]).then(resolve, reject);
}
});
}
}
复制代码
Promise.all
方法也是传入一个数组,一般传入多个promise,包装成一个promise返回,其状态有以下两种情况: 1 : 所有promise的状态都变成fulfilled时,返回的promise状态为fulfilled,结果是所有promise返回值的数组 2 : 有一个promise的状态率先变为rejected,返回的promise状态为rejected,结果是这个promise rejected的返回值
代码如下:
// Promise.all
export default function all(entries) {
// 初始化一个 Enumerator 实例 返回其属性 promise
return new Enumerator(this, entries).promise;
}
复制代码
Enumerator的具体代码如下:
export default class Enumerator {
constructor(Constructor, input) {
// Promise
this._instanceConstructor = Constructor;
// 新建一个空的promise 不会经过 initialPromise() 方法的初始化
// Promise.all() 最后返回这个值
this.promise = new Constructor(noop);
// 初始化
// promise[PROMISE_ID] = id++;
// promise._state = undefined;
// promise._result = undefined;
// promise._subscribers = [];
if (!this.promise[PROMISE_ID]) {
makePromise(this.promise);
}
if (isArray(input)) {
// 总长度
this.length = input.length;
// 剩余长度
this._remaining = input.length;
// 存放结果
this._result = new Array(this.length);
if (this.length === 0) {
// 空数组
// 返回一个状态为 fulfilled 的promise
fulfill(this.promise, this._result);
} else {
this.length = this.length || 0;
// 执行 _enumerate
this._enumerate(input);
if (this._remaining === 0) {
// 剩余长度为0
// 返回一个状态为 fulfilled 的promise
fulfill(this.promise, this._result);
}
}
} else {
// 入参不是数组
// 返回一个状态为r ejected 的promise
reject(this.promise, validationError());
}
}
_enumerate(input) {
// 遍历执行所有状态为 pending 的 promise
for (let i = 0; this._state === PENDING && i < input.length; i++) {
this._eachEntry(input[i], i);
}
}
_eachEntry(entry, i) {
// Promise.all() 方法 传入的是 Promise
let c = this._instanceConstructor;
// Promise.resolve
let { resolve } = c;
// Promise.resolve = Promise.resolve
if (resolve === originalResolve) {
// 尝试获取 entry 的 then 属性
let then = getThen(entry);
if (then === originalThen
&& entry._state !== PENDING) {
// then === Promise.prototype.then
// 情况1: entry是promise实例 同时 状态不为 pending
this._settledAt(entry._state, i, entry._result);
} else if (typeof then !== 'function') {
// then 不为函数 为普通值
// 直接设置
this._remaining--;
this._result[i] = entry;
} else if (c === Promise) {// true 在Promise.all() 中此处一直成立
let promise = new c(noop);
// then 是一个函数
// 对 then 进行进一步检查 如果是一个 thenable 对象
// let thenable = {
// then: function(resolve,reject)=>{ resolve('66')}
// }
// 则会执行 then 方法 获取相应的值后设置 promise 的状态
handleMaybeThenable(promise, entry, then);
// 设置监听
this._willSettleAt(promise, i);
} else {
// 情况2: entry是promise实例 状态为 pending
// 绑定监听 待entry状态改变之后 再设置相应的值
this._willSettleAt(new c(resolve => resolve(entry)), i);
}
} else {
// 其他情况 这里不作考虑
// 因为Promise.all 传入的是Promise
this._willSettleAt(resolve(entry), i);
}
}
_settledAt(state, i, value) {
let {
promise
} = this;
if (promise._state === PENDING) {
// 又完成了一个 Promise
this._remaining--;
if (state === REJECTED) {
// 一旦有一个promise实例状态变为rejected
// 更新状态为rejected
reject(promise, value);
} else {// fulfilled
// 更新值
this._result[i] = value;
}
}
if (this._remaining === 0) {
// 所有的元素都已经变为 fulfilled
// 同步返回promise的状态
fulfill(promise, this._result);
}
}
_willSettleAt(promise, i) {
let enumerator = this;
// 绑定监听
// promise的状态改变后 调用相应的函数
subscribe(
promise, undefined,
value => enumerator._settledAt(FULFILLED, i, value),
reason => enumerator._settledAt(REJECTED, i, reason)
);
}
};
复制代码
以上便是es6-promise核心代码的阅读。
最后
原文地址:链接
ES6-Promise仓库地址: 链接