彻底理解Promise-手动实现

252 阅读8分钟

Promise用法

image

Promise是ES6中新的异步语法,解决了回调地域的问题

new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve(1);
    }, 2000);
}).then(val => {
    console.log('val',val);
    return new Promise(resolve => {
        setTimeout(()=>{
            resolve(2);
        }, 2000)
    })
}).then(val => {
    console.log('val',val);
})
// 1
// 2

实现状态切换

  • Promise实例有三个状态,pending, fulfilled, rejected
  • Promise实例在构造是可以传入执行函数,执行函数有两个形参resolve, reject可以改变Promise状态,Promise的状态一旦改变后不可再进行改变
  • 执行函数会再创建Promise实例时,同步执行
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise2 {
    constructor (executor) {
        this.status = 'pending';
        this.value = null;
        this.reason = null;
        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
            }
        }
        executor(resolve, reject);
    }
}

let p = new Promise2((resolve, reject) => {
   setTimeout(() => {
       resolve(1)
   }, 2000)
})

实现then异步执行

Promise实例可以调用then方法并且传入回调:
如果调用then时,Promise实例是fulfilled状态,则马上异步执行传入的回调。
如果调用then时,Promise是pending状态,传入的回调会等到resolve后再异步执行

例子1 Promise是ffulfilled状态

let p = new Promise((resolve, reject) => {
    console.log(1);
    resolve(2);
    console.log(3);
})
    p.then(val => {
        console.log(val);
    })
// 执行顺序为 1 3 2、因为resolve之后是异步执行


例子2 Promise是pending状态

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    })
})
    p.then(val => {
        console.log(val);
    })
// 运行一秒后才会打印 1 因为需要等resolve执行完之后再打印

实现then异步执行思路: 需要用回调先保存到队列中,在resolve后异步执行队列里的回调,在then时判断实例的状态再决定是将回调推入队列,还是直接异步执行回调。

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise2 {
    constructor (executor) {
        this.status = 'pending';
        this.value = null;
        this.reason = null;
        this.onFulfilledCallbacks = []; // 成功的回调队列
        this.onRejectedcallbacks = []; // 失败的回调队列
        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                // 异步执行队列中的回调
                setTimeout(() => {
                    console.log('onFulfilledCallbacks',this.onFulfilledCallbacks);
                    this.onFulfilledCallbacks.forEach(callbacks => {
                        callbacks(this.value);
                    })
                })
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
                // 异步执行队列中的回调
                setTimeout(() => {
                    this.onRejectedcallbacks.forEach(callbacks => {
                        callbacks(this.reason);
                    })
                })
            }
        }
        executor(resolve, reject);
    }
    then (onFulfilled, onRejected) {
            // 状态是已完成的
        if (this.status === FULFILLED) {
            setTimeout(()=>{
                onFulfilled(this.value);
            })
        }
        // 状态是失败的
        if (this.staus === REJECTED) {
            setTimeout(() => {
                onRejected(this.reason);
            })
        }
        // 状态pending中
        if (this.status === PENDING) {
            this.onFulfilledCallbacks.push(onFulfilled); // 存储回调函数
            this.onRejectedcallbacks.push(onRejected);   // 存储回调函数
        }
    }
}

let p = new Promise2((resolve, reject) => {
  console.log(1);
  resolve(2);
  console.log(3);
})
p.then(val => {
    console.log(val);
})
//打印 1 3 2
let p = new Promise2((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
        console.log(2);
    })
})
    p.then(val => {
        console.log(val);
    })
// 打印 2 1

resolve Promise实例的情况

resolve的值有可能也是个Promise实例,这时候就要用前述实例自己resolve的值

let p = new Promise((resolve, reject) => {
    const p2 = new Promise((resolve2, reject2) => setTimeout(() => resolve2(1), 2000));
    resolve(p2);
})
    p.then(value => {
        console.log('val',value);
        // 两秒钟后输出 2
    })

因此需要再Promise1的resolve函数中进行判断,是Promise实例则在这个Promise实例(Promise2)后接一个then,并且将Promise1的resolve作为回调传入Promise2的then

const resolve = (value) => {
    // 判断value如果是否是Promise实例
    if (value instanceof  this.constructor) {
        // Promise2解决后,将外层的Promise也解决
        value.then(resolve, reject)
        return;
    }
    if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 异步执行队列中的回调
        setTimeout(() => {
            this.onFulfilledCallbacks.forEach(callbacks => {
                callbacks(this.value);
            })
        })
    }
}
let p = new Promise2((resolve, reject) => {
    const p2 = new Promise2((resolve2, reject2) => setTimeout(() => resolve2(1), 2000));
    resolve(p2);
})
    p.then(value => {
        console.log('val',value);
        // 两秒钟后输出 2
    })

实现链式调用

then可以链式调用,而且前一个then的回调的返回值,如果不是Promise实例,则下一个then回调的传参值就是上一个then回调的返回值,如果Promise实例,则下一个then回调的传参值,是上一个then回调返回的Promise实例的解决值(value)

let p = new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 2000)
})
p.then(val => {
    // 如果Promise实例,则下一个then回调的传参值,是上一个then回调返回的Promise实例的解决值(value)
    console.log('val', val);
    return new Promise(resolve => setTimeout(() => resolve(2), 2000));
}).then(val => {
    // 如果不是Promise实例,则下一个then回调的传参值就是上一个then回调的返回值
    console.log('val',val);
    return 3
}).then(val => {
    console.log('val',val);
})
// 打印结果是两秒后打印1 再两秒后2和3一起打印-

既然能够链式调用,那么then方法本身的返回值必定是一个Promise实例,那么返回的Promise实例是不是自身呢?答案显而易见:不是。如果一个Promise的then方法的返回值是Promise自身,在new一个Promise时,调用了resolve方法,因为Promise的状态一旦更改便不能再次更改,那么下面的所有的then便只能执行成功的回调,无法进行错误处理,这显然不符合Promise的规范和设计Promise的初衷。

因此then方法会返回一个新的Promise实例

then (onFulfilled, onRejected) {
    return new this.constructor((resolve, reject) => {
        // 状态是已完成的
        if (this.status === FULFILLED) {
            setTimeout(()=>{
                // 如果语法有错误也要reject出去
                try {
                    let callbakcValue = onFulfilled(this.value);
                    resolve(callbakcValue);
                }catch (e) {
                    reject(e);
                }
            })
        }
        // 状态是失败的
        if (this.staus === REJECTED) {
            setTimeout(() => {
                try {
                    let callBackValue = onRejected(this.reason);
                    resolve(callBackValue);
                }catch (e) {
                    reject(e);
                }
            })
        }
        // 状态pending中
        if (this.status === PENDING) {
            this.onFulfilledCallbacks.push(() => {
                try {
                    let callBackValue = onFulfilled(this.value);
                    resolve(callBackValue);
                }catch (e) {
                    reject(e)
                }
            }); // 存储回调函数
            this.onRejectedcallbacks.push(() => {
                try {
                    let callBackValue = onRejected(this.reason);
                    resolve(callBackValue);
                }catch (e) {
                    reject(e)
                }
            });   // 存储回调函数
        }
    });
}

let p = new Promise2((resolve, reject) => {
    setTimeout(() => resolve(1), 2000)
})
p.then(val => {
    // 如果Promise实例,则下一个then回调的传参值,是上一个then回调返回的Promise实例的解决值(value)
    console.log('val', val);
    return new Promise2(resolve => setTimeout(() => resolve(2), 2000));
}).then(val => {
    // 如果不是Promise实例,则下一个then回调的传参值就是上一个then回调的返回值
    console.log('val',val);
    return 3
}).then(val => {
    console.log('val',val);
})
// 打印结果是两秒后打印1 再两秒后2和3一起打印

实现catch-resolve-reject

calss Promise2 {
    static resolve (value) {
        // resolve中如果形参是个Promise,直接返回
        if (value instanceof this) {
            return value;
        }
        return new this((reslove, reject) => {
            reslove(value);
        })
    }

    static reject (reason) {
        // reject不用返回
        return new this((reslove, reject) => {
            reject(reason);
        })
    }
    constructor (executor) {...},
    then (onFulfilled, onRejected) {...},
    
    catch (onRejected) {
        return this.then(null, onRejected)
    }
}

// catch 方法
let p = new Promise2((resolve, reject) => {
    reject(1);
})
    p.catch(e => {
        console.log('e', e);
    })
// Promise.resolve的静态方法
let p = Promise2.resolve(1);
// Promise.reject的静态方法
let p = Promise2.reject(1)

实现all

calss Promise2 {
    static resolve (value) {...}
    static reject (reason) {...}
    static all (promises) {
      return new this((resolve, reject) => {
        let promiseNum = promises.length;
        let resolvedNum = 0;
        let resolvedValues = [...promises];
        for (let i = 0; i < promiseNum; i++) {
          this.resolve(promises[i])
            .then(val => {
                resolvedNum++;
                resolvedValues[i] = val;
                if (resolvedNum === promiseNum) {
                  // 所有的promise都已经解决
                  resolve(resolvedValues);
                }
              },
              reason => {
                reject(reason);
              });
        }
      });
    }
    constructor (executor) {...},
    then (onFulfilled, onRejected) {...},
    catch (onRejected) {...}
}
let p = Promise2.all([
new Promise2((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise2((resolve, reject) => setTimeout(() => resolve(2), 2000)),
new Promise2((resolve, reject) => setTimeout(() => resolve(3), 3000)),
]);
p.then(val => {
console.log('val', val);
});
// 6秒后打印 1 2 3

实现race

calss Promise2 {
    static resolve (value) {...}
    static reject (reason) {...}
    static all (promises) {...}
    static race (promises) {
      return new this((resolve, reject) => {
        let length = promises.length;
        for (let i = 0; i < length; i++) {
          this.resolve(promises[i])
            .then(
              val => {
                resolve(val);
              },
              reason => {
                reject(reason);
              }
            );
        }
      });
    }
    constructor (executor) {...},
    then (onFulfilled, onRejected) {...},
    catch (onRejected) {...}
}
let p = Promise2.race([
new Promise2((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise2((resolve, reject) => setTimeout(() => resolve(2), 2000)),
new Promise2((resolve, reject) => setTimeout(() => resolve(3), 3000)),
]);
p.then(val => {
console.log('val', val);
});
// 打印最快的返回结果 1

宏任务与微任务

macroTask(宏任务)是指将任务排到下一个事件循环
microTask(微任务)是指将任务排到当前事件循环的队尾,执行时机会比宏任务更早。
Promise的标准里没有规定Promise里的异步该使用哪种,但在node和浏览器的实现里都是使用的microTask(微任务)。
js是单线程所有主线程的同步任务先执行,然后执行微任务队列的程序,最后执行宏任务队列,秉承先进先出的原则。

image
image
宏任务Api包括: setTimeout(),setInterval(),setImmediate(nodeApi),requestAnimationFrame(),各种IO操作,网络请求。
微任务Api包括:process.nextTick(nodeApi),MutationObserver()。Promise.then catch finally

使用微任务替换上述中Promise中的setTimeout宏任务

  // 微任务方法
  let nextTick = (function () {
    let callbacks = [];
    let counter = 1;
    let node = document.createElement('div');

    function handler () {
      let copy = callbacks.slice();
      callbacks = [];
      copy.forEach(cb => cb());
    }

    let obServer = new MutationObserver(handler);
    obServer.observe(node, {
      childList: true,
    });
    return function (cb) {
      callbacks.push(cb);
      // 触发MutationObserver
      counter = (counter + 1) % 2;
      node.innerHTML = counter;
    };
  })();

  const PENDING = 'pending';
  const FULFILLED = 'fulfilled';
  const REJECTED = 'rejected';

  class Promise2 {
    // Promise.resolve方法
    static resolve (value) {
      // resolve中如果形参是个Promise,直接返回
      if (value instanceof this) {
        return value;
      }
      return new this((reslove, reject) => {
        reslove(value);
      });
    }
    // Promise.reject方法
    static reject (reason) {
      // reject不用返回
      return new this((reslove, reject) => {
        reject(reason);
      });
    }
    // Promise.all方法
    static all (promises) {
      return new this((resolve, reject) => {
        let promiseNum = promises.length;
        let resolvedNum = 0;
        let resolvedValues = [...promises];
        for (let i = 0; i < promiseNum; i++) {
          this.resolve(promises[i])
            .then(val => {
                resolvedNum++;
                resolvedValues[i] = val;
                if (resolvedNum === promiseNum) {
                  // 所有的promise都已经解决
                  resolve(resolvedValues);
                }
              },
              reason => {
                reject(reason);
              });
        }
      });
    }
    // Promise.race方法
    static race (promises) {
      return new this((resolve, reject) => {
        let length = promises.length;
        for (let i = 0; i < length; i++) {
          this.resolve(promises[i])
            .then(
              val => {
                resolve(val);
              },
              reason => {
                reject(reason);
              }
            );
        }
      });
    }

    constructor (executor) {
      this.status = 'pending';
      this.value = null;
      this.reason = null;
      this.onFulfilledCallbacks = []; // 成功的回调队列
      this.onRejectedcallbacks = []; // 失败的回调队列
      const resolve = (value) => {
        // 判断value如果是否是Promise实例
        if (value instanceof this.constructor) {
          // Promise2解决后,将外层的Promise也解决
          value.then(resolve, reject);
          return;
        }
        if (this.status === PENDING) {
          this.status = FULFILLED;
          this.value = value;
          // 异步执行队列中的回调
          nextTick(() => {
            this.onFulfilledCallbacks.forEach(callbacks => {
              callbacks(this.value);
            });
          });
        }
      };
      const reject = (reason) => {
        if (this.status === PENDING) {
          this.status = REJECTED;
          this.reason = reason;
          // 异步执行队列中的回调
          nextTick(() => {
            this.onRejectedcallbacks.forEach(callbacks => {
              callbacks(this.reason);
            });
          });
        }
      };
      executor(resolve, reject);
    }

    then (onFulfilled, onRejected) {
      return new this.constructor((resolve, reject) => {
        // 状态是已完成的
        if (this.status === FULFILLED) {
          nextTick(() => {
            // 如果语法有错误也要reject出去
            try {
              let callbakcValue = onFulfilled(this.value);
              resolve(callbakcValue);
            } catch (e) {
              reject(e);
            }
          });
        }
        // 状态是失败的
        if (this.status === REJECTED) {
          nextTick(() => {
            try {
              let callBackValue = onRejected(this.reason);
              resolve(callBackValue);
            } catch (e) {
              reject(e);
            }
          });
        }
        // 状态pending中
        if (this.status === PENDING) {
          this.onFulfilledCallbacks.push(() => {
            try {
              let callBackValue = onFulfilled(this.value);
              resolve(callBackValue);
            } catch (e) {
              reject(e);
            }
          }); // 存储回调函数
          this.onRejectedcallbacks.push(() => {
            try {
              let callBackValue = onRejected(this.reason);
              resolve(callBackValue);
            } catch (e) {
              reject(e);
            }
          });   // 存储回调函数
        }
      });
    }

    catch (onRejected) {
      return this.then(null, onRejected);
    }
  }

因为Promise本身使用的是微任务,所以我们需要把上述实现代码中的宏任务steTimeout替换成实现的微任务方法。这样会打印了1后再等待1秒,打印2后同时打印3,使用宏任务会打印2后,依次打印3,会有执行间隔。

网上的资料深浅不一,本人也是处在学习的过程中的总结,如果发现错误,欢迎留言指出~