Gadzan

手写Promise原理分析

分析Promise实现有利于理解异步函数调用过程和链式调用的实现.

最简单的Promise实现有7个主要属性, state(状态), value(成功返回值), reason(错误信息), resolve方法, reject方法, then方法.

简单代码实现

其中state有三个状态分别为: pending, fulfilledrejected.

一般情况下我们是这样使用Promise的: new Promise((resolve, reject) => {}).

我们使用new操作符生成Promise实例, 显然Promise是一个类.

在生成Promise实例时, 我们会把带有resolvereject两个参数的方法作为参数传给Promise, 因此Promise的构造函数初始化时, 有一个方法参数并在初始化时调用, 我们暂时把这个方法参数命名为executor.

首先我们让state的初始状态设为pending.

resolvereject都是方法, 当执行resolve(), 并且state状态为pending时, state状态变成fulfilled表示已成功. 这时, 把resolve的参数赋给value成功返回值.

而当执行reject(), 并且状态为pending时, state状态则变成rejected. 同时, 把reject的参数赋给reason错误信息.

then也有两个方法参数, 分别代表成功回调onFulfilled和失败回调onRejected.

例如:

p.then(
  (data) =>{
    console.log('Success:', data);
  },
  error => {
    console.log('Fail:', error);
  }
);

state状态是fulfilled的时候, 调用onFulfilled()方法把成功返回值value作为参数.

state状态是rejected的时候, 调用onRejected()方法把成功返回值reason作为参数.

按照上面思路, 很容易得出下面代码:

class Promise{
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
      }
    };
    try {
      // 立即执行函数
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
    if (this.state === 'fulfilled') {
      let x = onFulfilled(this.value);
    };
    if (this.state === 'rejected') {
      let x = onRejected(this.reason);
    };
  }
}

复杂代码实现:

但是then是被Promise多次调用的. 先过一遍复杂情况的代码:

class Promise{
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try {
      // 立即执行函数
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
    // onFulfilled如果不是函数,就忽略onFulfilled,变成默认直接返回value的函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // onRejected如果不是函数,就忽略onRejected,变成默认直接扔出错误的函数
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };

    // 声明返回的promise2
    let promise2 = new Promise((resolve, reject) => {
      
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          let x = onFulfilled(this.value);
          // resolvePromise 在后面有详细讲解怎么实现
          resolvePromise(promise2, x, resolve, reject);
        })
        this.onRejectedCallbacks.push(() => {
          let x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        })
      };

      if (this.state === 'fulfilled') {
        let x = onFulfilled(this.value);
        // resolvePromise函数,处理自己return的promise和默认的promise2的关系
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'rejected') {
        let x = onRejected(this.reason);
        resolvePromise(promise2, x, resolve, reject);
      };

    });
    // 返回promise,完成链式
    return promise2;
  }
  catch(fn){
    return this.then(null,fn);
  }
}

下面结合上面代码分析下复杂代码的实现.

复杂实现的改进:

then多次调用

为什么需要onResolvedCallbacksonRejectedCallbacks存储then方法的队列?

在不是链式调用的情况下, then方法可以多次被调用, 例如:

let p = new Promise((s, r) => {
  s('resoved.');
})
p.then(()=>{
  console.log('Run then');
})
p.then(()=>{
  console.log('2nd run then');
})

因此需要两个数组分别储存任务队列.

resolve方法改造后的流程:

  1. state(状态)在pending的情况下, 把state变成fulfiled(防止在其他状态下改变, 实现只能改变一次状态);
  2. value变量存储起来, 供以后then调用.
  3. 储在onResolvedCallbacks数组(队列)里的方法都运行一遍.

reject方法改造后的流程:

  1. state(状态)在pending的情况下, 把state变成rejected(防止在其他状态下改变, 实现只能改变一次状态);
  2. reason变量存储起来, 供以后then调用.
  3. 把存储在onRejectedCallbacks数组(队列)里的方法都运行一遍.

then链式调用

then不仅可以多次调用, 也可以链式调用, 例如:

p
  .then(()=>{console.log('Success.')})
  .then(()=>{console.log('and then.')})

因此then需要返回一个新的Promise实例, 并继续调用新实例的then方法形成链式.

理解起来的代码是这样的:

(p.then()).then()

那么下面我们在then里套娃, 创建新Promise实例, 并返回这个新的Promise实例, 如果还有下个then, 那就以此类推继续套娃, 目标都是为了执行外面传入的onFulfilled或者onRejected方法.

executor里的逻辑:

如果statepending,

  1. onFulfilled方法(即实例then的处理成功方法)放到onResolvedCallbacks数组队列里.
  2. onRejected方法(即实例then的处理失败方法)放到onRejectedCallbacks数组队列里.

如果statefulfilled, 则代入value立即执行onFulfilled方法.

如果staterejected, 则代入value立即执行onRejected方法.

如果在onFulfilledonRejected方法里返回了新的Promise实例需要继续往下执行, 那么onFulfilledonRejected的返回结果放到resolvePromise();判断并处理.

既然是用套娃的手段实现的, 就有可能把自己套进自己陷入死循环, 例如这样:

  var p2 = p.then(data => {
    return p2;
  })

我们把处理新Promise实例回调的部分分离成一个方法resolvePromise.

分析resolvePromise

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);
  }
}

resolvePromise 代码分析

p = new Promise(), p.then(rs, rj)返回了一个新的Promise实例(假设为p2), 可以让p2.then(rs2, rj2)继续执行...

rs运行后得到的结果为x(一般没有返回值, 所以为undefined), x代入resolvePromise()方法检验.

  1. 如果x是普通的值, 立刻执行resolve方法处理队列.
  2. 如果x有属性then并且then是函数, 那个可以被认为xPromise, 这种情况针对的是then里嵌套并返回新的Promise实例.
  3. 解决自己套自己的死循环, then里嵌套返回自身, then继续call自身然后返回自身, 进入死循环. 为了避免这种情况, 必须在resolvePromise判断是否是自身, 解决循环引用问题.

实例运行分析

const test = function() {
  return new Promise((res, rej)=>{
    setTimeout(()=>{
      console.log('ok');
      res();
    }, 2000)
  })
};
test().then(()=>{console.log(`1st then`)}).then(()=>{console.log(`2nd then`)});
  1. 当定时器结束时, 输出'ok', 执行了第一个Promise实例的resolve, 第一个Promise实例的state状态变成fulfilled;
  2. 第一个Promise实例的onResolvedCallbacks队列执行任务,
    1. 完成第一个thenonFulfilled方法, 并把返回的结果放到resolvePromise里执行, 这时的resolvereject函数是第二个Promise实例的(即then里的Promise实例);
      let x = onFulfilled(this.value);
      resolvePromise(promise2, x, resolve, reject);
    
    1. resolvePromise方法如果遇到普通值则执行第二个Promise实例的resolve方法, 将第二个Promise的状态从pending转为fulfilled, 然后执行第二个Promise实例的onResolvedCallbacks队列.
    2. 第二个Promise实例存在调用then, 因此它的onResolvedCallbacks队列里存放着第三个Promiseresolvereject方法并等待着第二个Promise实例的fulfilled状态改变(即resolve被调用).
    3. 因为没有第三个then, 因此第三个Promise实例的onResolvedCallbacks队列没有任务, 调用链到此为止.

其他Promise静态方法

//resolve方法
Promise.resolve = function(val){
  return new Promise((resolve,reject)=>{
    resolve(val)
  });
}
//reject方法
Promise.reject = function(val){
  return new Promise((resolve,reject)=>{
    reject(val)
  });
}
//race方法 
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    };
  })
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data);
      },reject);
    };
  });
}

打赏码

知识共享许可协议 本作品采用知识共享署名 4.0 国际许可协议进行许可。

评论