面试手写ES6的Promise

参考资源:
【翻译】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 ,但是需要一些小小的修正,因为上面有不对的地方。

  1. 执行顺序问题
    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));
  1. 抛出错误
new Promise((resolve,reject)=>{
    throw new Error("随便抛出一个错误");
    resolve(3);
})
.then(
    value=>console.log(value),
    reason=>console.log(reason)
);
ES6抛出错误

手写的直接报错:


手写的直接报错

原因在于 ES6 的错误是通过 reject 来触发的,我们手写的 Promise 遇到错误直接就抛出了,改写如下:

try{
    executor(resolve,reject);//不抛出错误
}catch(err){
    reject(err);//让reject 抛出
};
  1. 使用延时器不输出
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。到这里就关键了:

  1. 返回的是 _Promise 实例,如果想打点 then,必须的改变状态 state,所以返回 _Promise 实例的时候就已经调用 resolve 或 reject。
  2. 连续打点 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 输出结果是一样的。

解决小问题:

  1. 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);
}
  1. 返回值为 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+规范

所以真正符合 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);
  }
}

你可能感兴趣的:(面试手写ES6的Promise)