参考资源:
【翻译】Promises/A+规范-图灵社区
BAT前端经典面试问题:史上最最最详细的手写Promise教程 - 掘金
一、如何使用 Promise
先看一下 Promise 的用法。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("success");
});
});
promise.then(value => { console.log(value) }, reason => { console.log(reason) });
输出:
success
Promise 的构造函数接收了一个回调函数,这个回调就是下面要讲到的执行器(executor),executor 里面的 resolve, reject 也是两个函数,负责改变 Promise 实例的状态和它的值,then 函数中的回调在状态改变后执行,除此之外 then 还支持链式调用。
详见 ES6 Promise 的用法: http://es6.ruanyifeng.com
接下来就开始吧。
一、Promise 的三种状态
Promise 的三种状态:fulfilled(执行态)、rejected(拒绝态)、pending(等待态)。
关于状态转化:
- 成功时,不可转为其他状态,且必须有一个不可改变的值(value)
例如:new Promise((resolve, reject)=>{resolve(value)})
resolve 为成功,接收参数value,状态改变为 fulfilled,不可再次改变。
- 失败时,不可转为其他状态,且必须有一个不可改变的原因(reason)
例如:new Promise((resolve, reject)=>{reject(reason)})
,reject
为失败,接收参数 reason
,状态改变为 rejected
,不可再次改变。
第一步实现 executor
:
这块的执行逻辑是 new Promise
参数是一个函数体,executor
形参接收调用,executor
调用的时候需要传两个函数体过去,等待调用。
class _Promise {
constructor(executor){
// 校验executor
if(typeof executor !== "function"){
throw new Error(`Promise resolver ${executor} is not a function!`);
// new Promise(1); // Uncaught TypeError: Promise resolver 1 is not a function
};
const resolve = (value)=>{
console.log(value);
};
const reject = (reason)=>{
console.log(reason);
};
executor(resolve,reject);
}
}
new _Promise((resolve,reject)=>{
resolve("success");
});
第二步添加 Promise 的状态:
class _Promise {
constructor(executor){
// 校验executor
if(typeof executor !== "function"){
throw new Error(`Promise resolver ${executor} is not a function!`);
};
this.value = undefined; //终值=>resolve的值
this.reason = undefined;//拒因=>reject的值
this.state = "pending";//状态
const resolve = (value)=>{
// 成功后的一系列操作(状态的改变,成功回调的执行)
if(this.state === "pedding"){
this.state = "fulfilled";
this.value = value;
};
};
const reject = (reason)=>{
// 失败后的一系列操作(状态的改变,成功回调的执行)
if(this.state === "pedding"){
this.state = "rejected";
this.reason = reason;
}
};
executor(resolve,reject);
}
}
new _Promise((resolve,reject)=>{
resolve("success");
});
三、then 方法
then 方法,里面有两个参数:onFulfilled(成功时 resolve 触发)onFulfilled(失败时 reject 触发)
- 校验 onFulfilled 和 onFulfilled 是不是函数。是函数就直接运行,不是函数把值变成函数。
- resolve 函数执行的时候,把 state 的状态由 pending 变为 fulfilled,then 方法里面
state 的状态为 fulfilled 则执行 onFulfilled,同时传入 this.value。 - reject 函数执行的时候,把 state 的状态由 pending 变为 rejected,then 方法里面
state 的状态为 rejected则执行 onRejected,同时传入 this.value。
class _Promise {
constructor(executor){
// 校验executor
if(typeof executor !== "function"){
throw new Error(`Promise resolver ${executor} is not a function!`);
};
this.value = undefined; //终值=>resolve的值
this.reason = undefined;//拒因=>reject的值
this.state = "pending";//状态
const resolve = (value)=>{
// 成功后的一系列操作(状态的改变,成功回调的执行)
if(this.state === "pending"){
this.state = "fulfilled";
this.value = value;
};
};
const reject = (reason)=>{
// 失败后的一系列操作(状态的改变,成功回调的执行)
if(this.state === "pending"){
this.state = "rejected";
this.reason = reason;
}
};
executor(resolve,reject);
}
then(onFulfilled,onRejected){
// onFulfilled未传值或传的值不是function的时候
// 自动把onFulfilled变成一个函数
if(typeof onFulfilled !== "function"){
onFulfilled = value => value;
};
//onRejected未传值或传的值不是function的时候
//自动把onFulfilled变成一个函数,并抛出错误
if(typeof onRejected !== "function"){
onRejected = reason => { throw reason }
};
if(this.state === "fulfilled"){
onFulfilled(this.value);
};
if(this.state === "rejected"){
onRejected(this.reason);
};
}
};
四、调整执行策略
写到这里大致实现了 Promise ,但是需要一些小小的修正,因为上面有不对的地方。
- 执行顺序问题
ES6 的 promise 执行下面代码输出结果为:1 2 4 3
console.log(1);
new Promise((resolve,reject)=>{
console.log(2);
resolve(3);
})
.then(
value=>console.log(value)
);
console.log(4);
当使用我们自己手写的 Promise 运行时输出:1 2 3 4
console.log(1);
new _Promise((resolve,reject)=>{
console.log(2);
resolve(3);
})
.then(
value=>console.log(value)
);
console.log(4);
学过 JS 的事件执行机制的同学应该猜到哪里出现了问题了,没错我们手写的 then 这个方法是同步的,不会等到同步执行完再去执行 then 里面的函数。我们可以用 setTimeout 来解决这个问题。
setTimeout(()=>onFulfilled(this.value));
setTimeout(()=>onRejected(this.reason));
- 抛出错误
new Promise((resolve,reject)=>{
throw new Error("随便抛出一个错误");
resolve(3);
})
.then(
value=>console.log(value),
reason=>console.log(reason)
);
手写的直接报错:
原因在于 ES6 的错误是通过 reject 来触发的,我们手写的 Promise 遇到错误直接就抛出了,改写如下:
try{
executor(resolve,reject);//不抛出错误
}catch(err){
reject(err);//让reject 抛出
};
- 使用延时器不输出
new _Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(3);
});
})
.then(
value=>console.log(value)
);
ES6 会输出 3 ,手写的不输出结果。原因很简单,setTimeout 的执行会在 then 之后state 的状态是 pending,而 then 里面函数是根据 state 改变之后的状态来执行的。
class _Promise {
constructor(executor){
// 校验executor
if(typeof executor !== "function"){
throw new Error(`Promise resolver ${executor} is not a function!`);
};
this.value = undefined; //终值=>resolve的值
this.reason = undefined;//拒因=>reject的值
this.state = "pending";//状态
this.onFulfilledCallbacks = [];// 成功回调
this.onRejectedCallbacks = [];// 失败回调
const resolve = (value)=>{
// 成功后的一系列操作(状态的改变,成功回调的执行)
if(this.state === "pending"){
this.state = "fulfilled";
this.value = value;
this.onFulfilledCallbacks.forEach(fn=>fn(this.value));
};
};
const reject = (reason)=>{
// 失败后的一系列操作(状态的改变,成功回调的执行)
if(this.state === "pending"){
this.state = "rejected";
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn(this.reason));
}
};
try{
executor(resolve,reject);
}catch(err){
reject(err);
}
}
then(onFulfilled,onRejected){
// onFulfilled未传值或传的值不是function的时候
// 自动把onFulfilled变成一个函数
if(typeof onFulfilled !== "function"){
onFulfilled = value => value;
};
//onRejected未传值或传的值不是function的时候
//自动把onFulfilled变成一个函数,并抛出错误
if(typeof onRejected !== "function"){
onRejected = reason => { throw reason }
};
if(this.state === "pending"){
this.onFulfilledCallbacks.push(
(value)=>{
setTimeout(()=>onFulfilled(value))
}
);
this.onRejectedCallbacks.push(
(reason)=>{
setTimeout(()=>onRejected(reason))
}
);
};
if(this.state === "fulfilled"){
setTimeout(()=>onFulfilled(this.value));
};
if(this.state === "rejected"){
setTimeout(()=>onRejected(this.reason));
};
}
};
new _Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(3);
});
})
.then(
value=>console.log(value)
);
五、链式调用
这就太简单了,我们直接在 then 里面返回这个实例(return this)不就完了吗。就在将要收工的时候。测试:
new Promise((resolve,reject)=>{
resolve(3);
})
.then(value=>console.log(value))
.then()
.then(
value=>console.log(value)
)
// 3 undefined
而我们手写的运行上面的代码输出3 3
。
Promises/A+规范:
执行态(Fulfilled)
处于执行态时,promise 需满足以下条件:
- 不能迁移至其他任何状态
- 必须拥有一个不可变的终值
依据上面的规范,我们手写的 Promise 必须保证(终值不可变)也就是只能在一个 then 里面接收。所以直接 return this
这个方法不可行。那我们就 return new _Promise()
一个 then 对应一个状态不就行了。然后把 onFulfilled,onRejected
放到新 new 出来的 _Promise 里面执行。
同时把 then 函数里面执行的结果赋值给 x。到这里就关键了:
- 返回的是 _Promise 实例,如果想打点 then,必须的改变状态 state,所以返回 _Promise 实例的时候就已经调用 resolve 或 reject。
- 连续打点 then 的时候,比如:
new _Promise((resolve,reject)=>resolve(3)).then().then(value => console.log("value",value))
第一个 then 里面没有参数,resolve 里面的内容传给了 第二个 then 里面的 value。所以本例第一个then 没有传参数,onFulfilled 函数是 value=>value,返回的是 resolve 里面的值。这时我们的 resolve(x)。
这时候代码完成如下:
class _Promise {
constructor(executor){
// 校验executor
if(typeof executor !== "function"){
throw new Error(`Promise resolver ${executor} is not a function!`);
};
this.value = undefined; //终值=>resolve的值
this.reason = undefined;//拒因=>reject的值
this.state = "pending";//状态
this.onFulfilledCallbacks = [];// 成功回调
this.onRejectedCallbacks = [];// 失败回调
const resolve = (value)=>{
// 成功后的一系列操作(状态的改变,成功回调的执行)
if(this.state === "pending"){
this.state = "fulfilled";
this.value = value;
this.onFulfilledCallbacks.forEach(fn=>fn(this.value));
};
};
const reject = (reason)=>{
// 失败后的一系列操作(状态的改变,成功回调的执行)
if(this.state === "pending"){
this.state = "rejected";
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn(this.reason));
}
};
try{
executor(resolve,reject);
}catch(err){
reject(err);
}
}
then(onFulfilled,onRejected){
// onFulfilled未传值或传的值不是function的时候
// 自动把onFulfilled变成一个函数
if(typeof onFulfilled !== "function"){
onFulfilled = value => value;
};
//onRejected未传值或传的值不是function的时候
//自动把onFulfilled变成一个函数,并抛出错误
if(typeof onRejected !== "function"){
onRejected = reason => { throw reason }
};
const promise2 = new _Promise((resolve,reject)=>{
if(this.state === "pending"){
this.onFulfilledCallbacks.push(
(value)=>{
setTimeout(()=>{
const x = onFulfilled(value);
resolve(x);
})
}
);
this.onRejectedCallbacks.push(
(reason)=>{
setTimeout(()=>{
const x = onRejected(reason);
reject(x);
})
}
);
};
if(this.state === "fulfilled"){
setTimeout(()=>{
const x = onFulfilled(this.value);
resolve(x);
});
};
if(this.state === "rejected"){
setTimeout(()=>{
const x = onRejected(this.reason);
reject(x);
});
};
});
return promise2;
}
};
new _Promise((resolve,reject)=>{
resolve(3);
})
.then(
value => console.log("value",value)
)
.then(value => console.log("value",value))
输出结果:第一个 value 是 3 ,第二个 value 是 undefined,和 ES6 的 Promise 输出结果是一样的。
解决小问题:
- x === promise2
原生 ES6 测试下面代码:
let p = new Promise(resolve => {
resolve(0);
}).then(data => p);
报错TypeError: Chaining cycle detected for promise
ES6 的 Promise 会等待 p 的状态改变,而 p 一直在递归调用自己,所以报错。我们手写的没有这个功能。所以上面的代码在 _Promise 可以翻译为:如果 x === promise2,则是会造成循环引用,自己等待自己完成,则报错。
if(this.state === "fulfilled"){
setTimeout(()=>{
const x = onFulfilled(this.value);
// resolvePromise函数,处理自己return的promise和默认的promise2的关系
resolvePromise(promise2, x, resolve, reject);
});
};
resolvePromise函数:
function resolvePromise(promise2, x, resolve, reject){
// x 与 promise 相等
if (promise2 === x) {
return reject(new TypeError("Chaining cycle detected for promise"));
}
resolve(x);
}
- 返回值为 promise 时
new Promise((resolve,reject)=>{
resolve(3);
})
.then(
value => {
return new Promise((resolve,reject)=>{
resolve(1)
})
})
.then(value => console.log("value",value))
输出结果:value 1
,ES6 会等待新的 Promise 执行完毕。但是 _Promise 会返回一个
_Promise 实例,我们需要在这里等待新的 _Promise 执行拿到它执行的结果。再次改写 resolvePromise:
// 当x为_Promise时
if (x instanceof _Promise) {
x.then(
value => {
resolve(value);
},
reason => {
reject(reason)
}
);
}
你以为完成了是吧,嘻嘻,当我们这样:
new Promise((resolve,reject)=>{
resolve(3);
})
.then(
value => {
return new Promise((resolve,reject)=>{
resolve(new Promise((resolve,reject)=>{
resolve(1)
}))
})
})
.then(value => console.log("value",value))
很明显了,_promise 会输出 _Promise 实例,所以我们的递归,在再次改动:
// 当x为_Promise时
if (x instanceof _Promise) {
x.then(
value => {
resolvePromise(promise2, value, resolve, reject);
},
reason => {
reject(reason)
}
);
}else{
resolve(x)
}
到这里我们基本(因为没加防止多次调用,现在 reject 和 resolve 可同时调用,ES6中肯定是不能同时调用的)已经完成了手写 Promise ,只是 _Promise 的 resolvePromise 函数现在还不符合 Promises/A+规范,因为 Promises/A+规范对于 x 是这样要求的。
所以真正符合 Promises/A+规范的 resolvePromise:
function resolvePromise(promise2, x, resolve, reject){
// 循环引用报错
if(x === promise2){
// reject报错
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止多次调用
let called;
// x不是null 且x是对象或者函数
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
// A+规定,声明then = x的then方法
let then = x.then;
// 如果then是函数,就默认是promise了
if (typeof then === 'function') {
// 就让then执行 第一个参数是this 后面是成功的回调 和 失败的回调
then.call(x, y => {
// 成功和失败只能调用一个
if (called) return;
called = true;
// resolve的结果依旧是promise 那就继续解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
// 成功和失败只能调用一个
if (called) return;
called = true;
reject(err);// 失败了就失败了
})
} else {
resolve(x); // 直接成功即可
}
} catch (e) {
// 也属于失败
if (called) return;
called = true;
// 取then出错了那就不要在继续执行了
reject(e);
}
} else {
resolve(x);
}
}