本文中 promise 实现严格遵循 A+ 规范,并通过官方全部用例测试,详见promise A+ 官方文档
本文尽量由浅入深,从具体场景去剖析源码细节,如果感觉实在晦涩难懂,可以先移步至promise 基础学习基础用法。
术语直译
- “promise” is an object or function with a then method whose behavior conforms to this specification. 「promise 是一个其行为符合规范的对象或函数」
- “thenable” is an object or function that defines a then method. 「thenable 是一个拥有 then 方法的对象或函数」
- “value” is any legal JavaScript value (including undefined, a thenable, or a promise). 「resolve 或者 reject 的参数 value 是任何合法的 JavaScript 值(包括 undefined、thenable 或 promise)」
- “exception” is a value that is thrown using the throw statement. 「异常是使用 throw 语句抛出的值」
- “reason” is a value that indicates why a promise was rejected. 「reason 是一个值,表示 promise 被拒绝的原因」
基础用法介绍
// 前置基础知识
// @1 Promise 构造函数接收一个 executor 函数作为参数,它会立即执行
// @2 Promise 有三个状态,pending「等待态」、fulfilled「成功态」、reject「失败态」
// @3 只有在 pending 的状态时才能改变状态,不能从成功到失败,也不能从失败到成功
// @4 throw error 抛出异常也会执行失败的逻辑
var promise = new Promise((resolve, reject) => {
throw new Error('onError');
resolve('ok');
// reject('不ok');
});
promise.then(val => {
console.log(val, 'success');
}, reason => {
console.log(reason, 'fail');
});
// onError, 'fail'
那么,我们由此来写一个简版的 promise 吧~
乞丐版 promise
// 细节:敲黑板
// @1 因为每一个 promise 有自己的 resolve 和 reject 方法,所以两个方法没有放到原型上
// @2 resolve 和 reject 使用箭头函数,所以不用修正内部 this
// @3 then 中要用到成功或失败的入参,所以把 value 和 reason 挂在 this 上
// @4 executor 函数如果执行抛出异常,按照 reject(error) 来处理
// @5 promise 实例的状态只能从 pending 被改变一次
const PENDING = 'PENDING'; // 等待态「默认状态」
const FULFILLED = 'FULFILLED'; // 成功态
const REJECTED = 'REJECTED'; // 失败态
class _Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
if (this.status !== PENDING) return;
this.value = value;
this.status = FULFILLED;
}
const reject = (reason) => {
if (this.status !== PENDING) return;
this.reason = reason;
this.status = REJECTED;
}
try {
executor(resolve, reject);
} catch(e) {
reject(e);
}
}
// 原型上的 then 方法
then(onFulfilled, onRejected) {
if (this.status == FULFILLED) {
onFulfilled(this.value);
}
if (this.status == REJECTED) {
onRejected(this.reason);
}
}
}
// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;
测试代码
const _Promise = require('./_Promise.js');
const promise = new _Promise((resolve, reject) => {
throw new Error('error');
resolve('ok');
reject('不ok');
});
promise.then(val => {
console.log(val, 'success');
}, reason => {
console.log(reason, 'fail');
});
// error file
考虑异步执行 resolve 或 reject
promise 的状态往往都是异步更改的,比如请求接口成功后再调用 resolve 去改变 promise 的状态,如果按我们上面的实现,还没等到状态改变,then 方法就已经执行了,此时状态还是 pending,没有任何反应嘛。
所以我们要做一件事情,代码执行到 .then 时,我们不立即根据 promise 状态执行回调,而是把回调保存起来,状态改变的时候再来调用它「发布订阅」
// 考虑异步调用 resolve/reject 的版本
// @1 then 的时候,promise 实例状态还没改变,此时做回调方法收集
// @2 异步调用 resolve/reject 时,挨个执行回调『可能会有多个回调』
class _Promise {
constructor(executor) {
// ...
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
// ...
// 执行收集到的成功回调
this.onResolvedCallbacks.forEach(cb => cb(this.value));
}
const reject = (reason) => {
// ...
// 执行收集到的失败回调
this.onRejectedCallbacks.forEach(cb => cb(this.reason));
}
// ...
}
then(onFulfilled, onRejected) {
// ...
if (this.status == PENDING) {
// 异步改变 promise 状态时,回调方法收集
this.onResolvedCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
}
// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;
完整代码
// 考虑异步调用 resolve/reject 的版本
const PENDING = 'PENDING'; // 等待态「默认状态」
const FULFILLED = 'FULFILLED'; // 成功态
const REJECTED = 'REJECTED'; // 失败态
class _Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status !== PENDING) return;
this.value = value;
this.status = FULFILLED;
// 执行收集到的成功回调
this.onResolvedCallbacks.forEach(cb => cb(this.value));
}
const reject = (reason) => {
if (this.status !== PENDING) return;
this.reason = reason;
this.status = REJECTED;
// 执行收集到的失败回调
this.onRejectedCallbacks.forEach(cb => cb(this.reason));
}
try {
executor(resolve, reject);
} catch(e) {
reject(e);
}
}
// 原型上的 then 方法
then(onFulfilled, onRejected) {
if (this.status == FULFILLED) {
onFulfilled(this.value);
}
if (this.status == REJECTED) {
onRejected(this.reason);
}
if (this.status == PENDING) {
// 异步改变 promise 状态时,回调方法收集
this.onResolvedCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
}
// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;
测试代码
const promise = new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000)
});
promise.then(val => {
console.log(val, 'success');
}, reason => {
console.log(reason, 'fail');
});
// 多回调场景
promise.then(val => {
console.log(val, 'success');
}, reason => {
console.log(reason, 'fail');
});
// 1s 后输出两个 "1 success"
实现链式调用
// 前置知识
// @1 then 返回的总是一个包装好的 promise 对象,传递给下一个 then 方法,遵循如下规则:
// + 如果 then 内部返回的就是一个 promise,则采用返回的 promise 的状态和值往下传递
// + 如果 then 返回一个普通值『不是 promise』,则相当于返回 Promise.resolve(val)
// + 如果 then 方法语法报错或抛出一个错误,则相当于 Promise.reject(error)
// @2 实现链式调用的关键在于,总是返回一个全新的 promise2 对象,来保证状态可以正常切换
// + 比如在成功回调中返回了一个失败的 promise,如果返回 this『原 promise 对象』
// 就会出现状态不能从成功改到失败的状况。
// @3 返回的 promsie2 中的 executor 函数是立即执行的,而我们也刚好要拿到上一轮的
// promise 结果,来决定是调用 promise2.reslove 还是 promise2.reject,用 x 来
// 表示上一轮的返回结果,我们可以取巧的把 then 内部代码都搬到 promise2 的 executor
// 方法中来执行,这样就可以直接根据返回的结果 x,判断如何更改 promise2 的状态,
// 并且把 x 往下传递
// @4 我们声明一个 resolvePromise 方法,用来处理往下传递的 x
class _Promise {
constructor(executor) {
const resolve = (value) => {
// ...
this.onResolvedCallbacks.forEach(cb => cb());
}
const reject = (reason) => {
// ...
this.onRejectedCallbacks.forEach(cb => cb());
}
// ...
}
// 原型上的 then 方法
then(onFulfilled, onRejected) {
let promise2 = new _Promise((resolve, reject) => {
if (this.status == FULFILLED) {
// 注意,这里使用 promise2,必须异步获取(new 的过程中使用 实例)
setTimeout(() => {
try {
let x = onFulfilled(this.value);
// 根据 x 类型,判断 promise2 的状态和值
resolvePromise(x, promise2, resolve, reject);
} catch(e) {
// 上轮链式执行报错, 走本轮的失败回调
reject(e);
}
}, 0)
}
if (this.status == REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch(e) {
reject(e);
}
}, 0);
}
if (this.status == PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch(e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
}
}
// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;
完整代码
// 实现链式调用
const resolvePromise = require('./resolvePromise');
const PENDING = 'PENDING'; // 等待态「默认状态」
const FULFILLED = 'FULFILLED'; // 成功态
const REJECTED = 'REJECTED'; // 失败态
class _Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status !== PENDING) return;
this.value = value;
this.status = FULFILLED;
this.onResolvedCallbacks.forEach(cb => cb());
}
const reject = (reason) => {
if (this.status !== PENDING) return;
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach(cb => cb());
}
try {
executor(resolve, reject);
} catch(e) {
reject(e);
}
}
// 原型上的 then 方法
then(onFulfilled, onRejected) {
let promise2 = new _Promise((resolve, reject) => {
if (this.status == FULFILLED) {
// 注意,这里使用 promise2,必须异步获取(new 的过程中使用 实例)
setTimeout(() => {
try {
let x = onFulfilled(this.value);
// 根据 x 类型,判断 promise2 的状态和值
resolvePromise(x, promise2, resolve, reject);
} catch(e) {
// 上轮链式执行报错, 走本轮的失败回调
reject(e);
}
}, 0)
}
if (this.status == REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch(e) {
reject(e);
}
}, 0);
}
if (this.status == PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch(e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
}
}
// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;
// @1 根据上一轮 then 的返回结果 x,来判断如何往下传递新 promise 对象『promise2』的状态和值
// + 如果 x 就是一个 promise,则采用返回的 promise 的状态和值往下传递
// + 如果 x 是一个普通值『不是 promise』,返回 promise2.resolve(val)
// @2 我们还需要考虑别人实现的 promise 的各种奇葩情况💦
function resolvePromise(x, promise2, resolve, reject) {
// 根据规范 2.3.1,如果返回值 x 就是 promise2,这就会导致循环引用
// let promise2 = new Promise(resolve => resolve('ok')).then(data => {
// return promise2;
// })
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 根据规范2.3.3.3 为了防止某些库可以调用两次 resolve 或 reject「不规范的实现」,加个锁
let called = false;
// 严格按照规范 2.3.3
// 判断 promise 是一个 thenable 的对象类型还是普通值
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
// 是对象,判断是否存在 x.then,且 then 是否为一个函数
let then = x.then;
if (then && typeof then === 'function') {
// thenable 对象成立,我们认为 x 就是个 promise 对象,这时根据 x 的状态决定接下来传递的 promise2 的状态
// 这里调用 then 时,不使用 x.then 的原因是防止有些库,取第二次 then 属性抛出一个错误
// 根据 2.3.3.3
then.call(x, y => {
if (called) return;
called = true;
// 成功回调 y 有可能还是一个 promise「套娃」,要递归解析,直到得到普通值
resolvePromise(y, promise2, resolve, reject);
}, r => {
if (called) return;
called = true;
// 失败回调
reject(r);
});
} else {
// 比如 x 是 { then: 1 },这时候会把 x 作为值,promise2 改为成功态
resolve(x);
}
} catch (e) {
// 如果某些库劫持了 x.then 的 get 方法,主动抛出一个错误
reject(e);
}
} else {
// 是值类型
resolve(x);
}
}
module.exports = resolvePromise;
测试代码
let promise = new _Promise(resolve => resolve('ok')).then(data => {
return new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(100);
}, 1000);
});
})
promise.then(data => {
console.log(data);
}, reason => {
console.log('err', reason)
});
// 1s 后输出 100
实现值的穿透
如果在 then 链式调用中,我们省略了某个成功(onFulfilled)或者失败(onRejected)的方法,这时候会进行值的穿透。
let promise = new Promise(resolve => {
resolve('ok')
}).then().then().then(data => {
console.log(data);
})
// ok
这个实现就较为简单了,我们只需要判断 onFulfilled 或者 onRejected 是不是一个函数类型,做对应处理即可。
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason };
// ...
}
测试代码
let promise = new _Promise((resolve, reject) => {
// resolve('ok')
// reject('fail');
throw new Error('error');
}).then().then().then(data => {
console.log(data);
}, reason => {
console.log('reason', reason);
})
// reason error
至此,promise A+ 规范中的所有代码已经实现完毕,catch、finally、all 等方法不属于规范,只是一些扩展方法,详见手撕 promise 扩展方法
promise A+ 规范测试
// 测试方法
// @1 在我们实现的 _Promise 文件底部声明以下静态方法,它会帮我们测试 dfd 对象的
// promise、resolve、reject 是否符合规范
// @2 安装 promises-aplus-tests 包,package 中添加 scripts:
// promises-aplus-tests _Promise4/_Promise4.js (自己的源码路径)
_Promise.deferred = function() {
let dfd = {};
dfd.promise = new _Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
package.json 中添加一个命令
"scripts": {
"test": "promises-aplus-tests _Promise4/_Promise4.js"
}
执行 npm run test
完整代码
完整代码
// _Promise4.js
const resolvePromise = require('./resolvePromise');
const PENDING = 'PENDING'; // 等待态「默认状态」
const FULFILLED = 'FULFILLED'; // 成功态
const REJECTED = 'REJECTED'; // 失败态
class _Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status !== PENDING) return;
this.value = value;
this.status = FULFILLED;
this.onResolvedCallbacks.forEach(cb => cb());
}
const reject = (reason) => {
if (this.status !== PENDING) return;
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach(cb => cb());
}
try {
executor(resolve, reject);
} catch(e) {
reject(e);
}
}
// 原型上的 then 方法
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason };
let promise2 = new _Promise((resolve, reject) => {
if (this.status == FULFILLED) {
// 注意,这里使用 promise2,必须异步获取(new 的过程中使用 实例)
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject); // 根据 x 类型,判断 promise2 的状态和值
} catch(e) {
reject(e); // 上轮链式执行报错, 走本轮的失败回调
}
}, 0)
}
if (this.status == REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject); // 根据 x 类型,判断 promise2 的状态和值
} catch(e) {
reject(e); // 上轮链式执行报错, 走本轮的失败回调
}
}, 0);
}
if (this.status == PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject); // 根据 x 类型,判断 promise2 的状态和值
} catch(e) {
reject(e); // 上轮链式执行报错, 走本轮的失败回调
}
}, 0)
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject); // 根据 x 类型,判断 promise2 的状态和值
} catch(e) {
reject(e); // 上轮链式执行报错, 走本轮的失败回调
}
}, 0);
});
}
});
return promise2;
}
}
_Promise.deferred = function() {
let dfd = {};
dfd.promise = new _Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;
// @1 根据上一轮 then 的返回结果 x,来判断如何往下传递新 promise 对象『promise2』的状态和值
// + 如果 x 就是一个 promise,则采用返回的 promise 的状态和值往下传递
// + 如果 x 是一个普通值『不是 promise』,返回 promise2.resolve(val)
// @2 我们还需要考虑别人实现的 promise 的各种奇葩情况💦
function resolvePromise(x, promise2, resolve, reject) {
// 根据规范 2.3.1,如果返回值 x 就是 promise2,这就会导致循环引用
// let promise2 = new Promise(resolve => resolve('ok')).then(data => {
// return promise2;
// })
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 严格按照规范 2.3.3
// 判断 promise 是一个 thenable 的对象类型还是普通值
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// 根据规范2.3.3.3 为了防止某些库可以调用两次 resolve 或 reject「不规范的实现」,加个锁
let called = false;
try {
// 是对象,判断是否存在 x.then,且 then 是否为一个函数
let then = x.then;
if (typeof then === 'function') {
// thenable 对象成立,我们认为 x 就是个 promise 对象,这时根据 x 的状态决定接下来传递的 promise2 的状态
// 这里调用 then 时,不使用 x.then 的原因是防止有些库,取第二次 then 属性抛出一个错误
// 根据 2.3.3.3
then.call(x, y => {
if (called) return;
called = true;
// 成功回调 y 有可能还是一个 promise「套娃」,要递归解析,直到得到普通值
resolvePromise(y, promise2, resolve, reject);
}, r => {
if (called) return;
called = true;
// 失败回调
reject(r);
});
} else {
// 比如 x 是 { then: 1 },这时候会把 x 作为值,promise2 改为成功态
// 不存在调用多次情况 他不是一个 promise
resolve(x);
}
} catch (e) {
// 如果某些库劫持了 x.then 的 get 方法,主动抛出一个错误
// 不能失败了再失败 两次调用只走一次
if (called) return;
called = true;
reject(e);
}
} else {
// 是值类型 不存在调用多次情况 他不是一个 promise
resolve(x);
}
}
module.exports = resolvePromise;
思考
如果 constructor 中的 resolve 方法继续接收一个异步返回结果的 promise 呢,我们在 resolve 中默认把 value 当成值类型直接赋值了
let p = new _Promise((resolve, reject) => {
resolve(new _Promise(resolve => {
setTimeout(() => {
resolve(100);
}, 1000)
}));
}).then(val => {
console.log(val);
});
// _Promise { status: 'PENDING', value: undefined, reason: undefined }
不过这个不属于 promise A+ 规范,我们在下一篇文章实现静态方法 resolve 时,会对它进行修复。 传送阵: 手撕 promise 扩展方法