Promise原理剖析及模拟实现

发布于 2021-02-28  160 次阅读 本文共11457个字


前边《Promise详解》中我们对Promise做了介绍,这篇文章我们再来深入探讨下Promise的原理,并自己模拟实现下。

首先,Promise肯定是一个类,有三个状态,pending、resolved、rejected。状态改变要么是从pending变为resolved为成功,要么从pending变为rejected为失败。

那我们如何实现Promise呢,我们可以参考《Promises/A+》,这本书只是一个规范,当多方封装的promise都遵循规范时才尽可能通用,接下来我们就根据文档去实现Promise。

Promise

一步步实现,先来实现 2.1 章节中的 Promise States

// 为了防止拼错,我们的状态在这都统一定义为常量
const PENDING = 'pending'; // 初始态
const FULFILLED = 'fullfilled'; // 成功态
const REJECTED = 'rejected'; // 失败态

function Promise(executor) { // executor为执行函数
  let self = this; // 先缓存当前的Promise实例
  self.status = PENDING; // 设置状态
  self.onResolvedCallbacks = []; // 定义存放成功的回调的数组
  self.onRejectedCallbacks = []; // 定义存放失败回调的数组

  // 当调用此方法的时候,如果promise状态为pending的话,可以转为resolve成功态,如果失败,则return
  function resolve(value) {
    // 如果是初始态,则转为成功态
    if (self.status === PENDING) {
      self.status = FULFILLED;
      self.value = value; // 成功后会得到一个值,这个值不能改
      self.onResolvedCallbacks.forEach((cb) => cb(self.value)); // 调用成功的回调
    }
  }

  // 文档中说失败态需要有reason
  function reject(reason) {
    // 如果是初始态,则转为失败态
    if (self.status === PENDING) {
      self.status = REJECTED;
      self.value = reason; // 状态改为失败后,需要将失败原因reason给到value
      self.onRejectedCallbacks.forEach((cb) => cb(self.value)); // 调用失败的回调
    }
  }

  // 因为exector为用户手动传入,函数执行可能会异常,所以需要捕获,如果出错了,将promise进入reject失败态
  try {
    exector(resolve, reject)
  } catch(e) {
    // 如果这个函数失败了,失败的原因reject这个promise
    reject(e)
  }
}

Promise.then

然后我们接着来实现 2.2章的The then Method

// onFullfilled 和 onRejected 分别为Promise成功或者失败后的回调
Promise.prototype.then = function(onFullfilled, onRejected) {
  // 如果成功或者失败的回调没有传,则表示then里面不处理任何逻辑,就直接把值往后传递即可
  onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : (value) => { return value };
  onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason };
  // 如果当前promise的状态已经是成功状态了,则onFullfilled直接取值
  if (self.status === FULFILLED) {
    let res = onFullfilled(self.value);
  }

  if (self.status === REJECTED) {
    let res = onRejected(self.value);
  }

  // 如果当前的成功失败态还未拿到,所以要处理pending状态,pending以后再拿到resolve或reject
  if (self.status === PENDING) {
    self.onResolvedCallbacks.push(onFullfilled);
    self.onRejectedCallbacks.push(onRejected);
  }
}

写到了这里我们还不知道运行到底怎么样呢,我们来写个case来测试一下。分写在then的几个状态里边加上console来做测试,如:

if (self.status === FULFILLED) {
  console.log('fullfilled')
}

if (self.status === REJECTED) {
  console.log('rejected')
}

if (self.status === PENDING) {
  console.log('pending')
}
let myPromise = require('./promise');
let p1 = new myPromise(function(resolve, reject) {
  setTimeout(function() {
    let num = Math.random();
    if (num > 0.5) {
      resolve(num)
    } else {
      reject('error')
    }
  })
});

p1.then(function(data) {
  console.log(data)
}, function(error) {
  console.error(error)
})

我们上边这个case是异步的,所以等我们执行then的时候,promise.then的时候还没有成功失败状态呢,所以肯定是pending状态,控制台打印出来'pending'。

// run code 查看结果:
// 第一次执行打印结果:
pending
0.04635508746350747

// 第二次执行打印结果:
pending
error

// 第三次执行打印结果:
pending
0.3629381823423041

...

上述是我自己测试的结果,代码都正常运行,打印逻辑看来也正确。但我们可以看到状态都走的是pending状态,为什么呢?是因为我们前边的case用的是异步,如果我们想直接测试fullfilled状态和rejected状态,则将case里的setTimeout去掉改为同步即可直接进入状态。

测试结果都正常,所以我们继续根据文档完善then。

// 代码习惯,这里我们将Promise的状态都统一定义为常量
const PENDING = 'pending'; // 初始态
const FULFILLED = 'fullfilled'; // 成功态
const REJECTED = 'rejected'; // 失败态

function Promise(executor) { // executor 为执行函数
  let self = this; // 先缓存当前的Promise实例
  self.status = PENDING; // 设置初始状态
  self.onResolvedCallbacks = []; // 定义存放成功的回调的数组
  self.onRejectedCallbacks = []; // 定义存放失败回调的数组

  // 当调用此方法的时候,如果promise状态为pending的话,可以转为resolve成功态,如果失败,则return
  function resolve (value){
    if (value !== null && value.then && typeof value.then === 'function' {
        return value.then(resolve, reject)
    }
    // 如果是初始态,则转为成功态
    // 异步处理,直接用setTimeout就可
    setTimeout(function() {
      if (self.status === PENDING) {
        self.status = FULFILLED;
        self.value = value; // 成功后会得到一个值,这个值不能改
        self.onResolvedCallbacks.forEach((cb) => cb(self.value)); // 调用成功的回调
      }
    })
  }

  // 文档中说失败态需要有reason
  function reject(reason) {
    // 如果是初始态,则转为失败态
    setTimeout(function() {
      if (self.status === PENDING) {
        self.status = REJECTED;
        self.value = reason; // 状态改为失败后,需要将失败原因reason给到value
        self.onRejectedCallbacks.forEach((cb) => cb(self.value)); // 调用失败的回调
      }
    })
  }

  // 因为executor为手动传入,函数执行可能会异常,所以需要捕获,如果出错了,将promise进入reject失败态
  try {
    // 成功则函数执行
    executor(resolve,reject);
  } catch(e) {
    // 如果这个函数失败了,失败的原因reject这个promise
    reject(e)
  }
}

// onFulfilled 用来接收Promise成功或者失败的原因
Promise.prototype.then = function(onFullfilled, onRejected) {
  // 如果成功或者失败的回调没有传,则表示then里面不处理任何逻辑,就直接把值往后传递即可
  onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : (value) => { return value };
  onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason };
  // 如果当前promise的状态已经是成功状态了,则onFullfilled直接取值
  let self = this;
  let promise2;
  if (self.status === FULFILLED) {
    console.log('then---fullfilled')
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          // res可能是一个promise对象,也可能是一个普通值
          let x = onFullfilled(self.value);
          // 如果获取到了返回值x,会走解析promise的过程
          resolvePromise(promise2, x, resolve, reject);
        } catch(e) {
          // 如果执行成功的回调过程中出现错误,则reject错误原因
          reject(e)
        }
      })
    })
  }

  if (self.status === REJECTED) {
    console.log('then---rejected')
    // let x = onRejected(self.value);
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          let x = onRejected(self.reason)
          resolvePromise(promise2, x, resolve, reject);
        } catch(e) {
          reject(e)
        }
      })
    })
  }

// 如果当前的成功失败态还未拿到,所以要处理pending状态,pending以后再拿到resolve或reject
  if (self.status === PENDING) {
    console.log('then---pending')
    self.onResolvedCallbacks.push(function() {
      // 同上这里也需要try catch
      try {
        let x = onFullfilled(self.value);
        // 如果取到返回值x,则会走promise解析的过程
        resolvePromise(promise2, x, resolve, reject)
      } catch(e) {
        reject(e)
      }
    });
    self.onRejectedCallbacks.push(function() {
      try {
        let x = onRejected(self.value);
        resolvePromise(promise2, x. resolve, reject);
      } catch(e) {

      }
    });
  }
}
module.exports = Promise;

上段代码中处理then的过程中有两处用到resolvePromise,接下来我们就根据文档中的2.3 章节The Promise Resolution Procedure来继续实现。

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('循环引用'));
  }
  let called = false; // promise2 状态是否已经为 resolve 或 reject
  if (x instanceof Promise) {
    if (x.status === PENDING) {
      x.then(function(y) {
        resolvePromise(promise2, x, resolve, reject);
      }, reject)
    } else {
      x.then(resolve, reject);
    }
  } else if (x !== null && ((typeof x === 'object') || (typeof x === 'function'))) {
    // x是一个thenable对象或者函数,只要有then方法的对象
    // 有时候我们的promise需要可能需要别的promise库进行交互使用,所以编写这段代码的时候我们需要尽可能的考虑到兼容性
    try {
      let then = x.then;
      if (typeof then === 'function') {
        // 有些promise会同时执行成功或者失败的回调
        then.call(x, function(y) {
          // 如果promise2已经转为成功或失败状态,则不再处理
          if (called) return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, function(err) {
          if (called) return;
          called = true;
          reject(err);
        });
      } else {
        // 这里的话x不是一个thenable对象,那直接将值进行resolve
        resolve(x);
      }
    } catch(e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 如果x是一个普通值,则resolve x
    resolve(x)
  }
}

上述中的循环引用,有可能在使用的过程中错误的使用导致自己需要等待自己完成,则一直不可能完成了,所以需要做判断处理,举栗:

let p2 = p1.then(function(data) {
  console.log(data);
  return p2;
})

上述例子中p1.then()的返回值为p2, 而这时候如果promise不手动调用resolve方法,就无法修改状态,那就一直处在pending了。

前边在实现的过程中我们用到setTimeout来起到异步执行的作用,《Promises/A+》中第3章 Notes中也有说明,直接看文档即可。

现在我们来看一下整体代码:

// 代码习惯,这里我们将Promise的状态都统一定义为常量
const PENDING = 'pending'; // 初始态
const FULFILLED = 'fullfilled'; // 成功态
const REJECTED = 'rejected'; // 失败态

function Promise(executor) { // executor 为执行函数
  let self = this; // 先缓存当前的Promise实例
  self.status = PENDING; // 设置初始状态
  self.onResolvedCallbacks = []; // 定义存放成功的回调的数组
  self.onRejectedCallbacks = []; // 定义存放失败回调的数组

  // 当调用此方法的时候,如果promise状态为pending的话,可以转为resolve成功态,如果失败,则return
  function resolve (value){
    if (value !== null && value.then && typeof value.then === 'function' {
        return value.then(resolve, reject)
    }
    // 如果是初始态,则转为成功态
    // 异步处理,直接用setTimeout就可
    setTimeout(function() {
      if (self.status === PENDING) {
        self.status = FULFILLED;
        self.value = value; // 成功后会得到一个值,这个值不能改
        self.onResolvedCallbacks.forEach((cb) => cb(self.value)); // 调用成功的回调
      }
    })
  }

  // 文档中说失败态需要有reason
  function reject(reason) {
    // 如果是初始态,则转为失败态
    setTimeout(function() {
      if (self.status === PENDING) {
        self.status = REJECTED;
        self.value = reason; // 状态改为失败后,需要将失败原因reason给到value
        self.onRejectedCallbacks.forEach((cb) => cb(self.value)); // 调用失败的回调
      }
    })
  }

  // 因为executor为手动传入,函数执行可能会异常,所以需要捕获,如果出错了,将promise进入reject失败态
  try {
    // 成功则函数执行
    executor(resolve,reject);
  } catch(e) {
    // 如果这个函数失败了,失败的原因reject这个promise
    reject(e)
  }
}

// onFulfilled 用来接收Promise成功或者失败的原因
Promise.prototype.then = function(onFullfilled, onRejected) {
  // 如果成功或者失败的回调没有传,则表示then里面不处理任何逻辑,就直接把值往后传递即可
  onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : (value) => { return value };
  onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason };
  // 如果当前promise的状态已经是成功状态了,则onFullfilled直接取值
  let self = this;
  let promise2;
  if (self.status === FULFILLED) {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          // res可能是一个promise对象,也可能是一个普通值
          let x = onFullfilled(self.value);
          // 如果获取到了返回值x,会走解析promise的过程
          resolvePromise(promise2, x, resolve, reject);
        } catch(e) {
          // 如果执行成功的回调过程中出现错误,则reject错误原因
          reject(e)
        }
      })
    })
  }

  if (self.status === REJECTED) {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          let x = onRejected(self.reason)
          resolvePromise(promise2, x, resolve, reject);
        } catch(e) {
          reject(e)
        }
      })
    })
  }

  // 如果当前的成功失败态还未拿到,所以要处理pending状态,pending以后再拿到resolve或reject
  if (self.status === PENDING) {
    self.onResolvedCallbacks.push(function() {
      // 同上这里也需要try catch
      try {
        let x = onFullfilled(self.value);
        // 如果取到返回值x,则会走promise解析的过程
        resolvePromise(promise2, x, resolve, reject)
      } catch(e) {
        reject(e)
      }
    });
    self.onRejectedCallbacks.push(function() {
      try {
        let x = onRejected(self.value);
        resolvePromise(promise2, x. resolve, reject);
      } catch(e) {

      }
    });
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('循环引用'));
  }
  let called = false; // promise2 状态是否已经为 resolve 或 reject
  if (x instanceof Promise) {
    if (x.status === PENDING) {
      x.then(function(y) {
        resolvePromise(promise2, x, resolve, reject);
      }, reject)
    } else {
      x.then(resolve, reject);
    }
  } else if (x !== null && ((typeof x === 'object') || (typeof x === 'function'))) {
    // x是一个thenable对象或者函数,只要有then方法的对象
    // 有时候我们的promise需要可能需要别的promise库进行交互使用,所以编写这段代码的时候我们需要尽可能的考虑到兼容性
    try {
      let then = x.then;
      if (typeof then === 'function') {
        // 有些promise会同时执行成功或者失败的回调
        then.call(x, function(y) {
          // 如果promise2已经转为成功或失败状态,则不再处理
          if (called) return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, function(err) {
          if (called) return;
          called = true;
          reject(err);
        });
      } else {
        // 这里的话x不是一个thenable对象,那直接将值进行resolve
        resolve(x);
      }
    } catch(e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 如果x是一个普通值,则resolve x
    resolve(x)
  }
}

module.exports = Promise;

Promise.all

Promise.all 传入一个数组, 需要等待所有的promise状态fullfilled之后才resolve,有一个失败则reject。

Promise.prototype.all = function(promiseArr) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promiseArr)) {
      throw new Error('argument must be an array');
    }
    let resultArr = [];
    let count = 0;
    for(let i = 0; i < promiseArr.length; i++) {
      promiseArr[i].then(function(data) {
        resultArr.push(data);
        count++;
        if (count === promiseArr.length) {
          return resolve(data)
        }
      }).catch((err) => {
        reject(err)
      })
    }
  })
}

Promise.race

race顾名思义就是赛跑的意思,只要是第一个得到的结果,不管是成功还是失败,直接返回。

Promise.prototype.race = function(promiseArr) {
  if (!Array.isArray(promiseArr)) {
    throw new Error('argument must be an array');
  };
  return new Promise(function(resolve, reject) {
    for(let i = 0; i < promiseArr.length; i++) {
      promiseArr[i].then(resolve, reject);
    };
  });
};

Promise.resolve

返回一个立刻成功的promise

加入别人提供一个方法,需要你传一个promise,但你只有一个普通值,就可以用到此方法,将普通值(string,number,Object等)转换为Promise对象。

Promise.prototype.resolve = function(value) {
  return new Promise(function(resolve) {
    resolve(value);
  });
};

Promise.reject

与resolve相反,返回一个立即失败的promise

Promise.prototype.reject = function(reason) {
  return new Promise(function(resolve, reject) {
    reject(reason);
  });
};

Promise.catch

catch原理:只传失败回调

Promise.prototype.catch = function(onRejected) {
  this.then(null, onRejected);
};

Promise.retry

retry是报错尝试,当尝试超过一定次数以后才是真正的reject

Promise.prototype.retry = function(getData, times, delay) {
  return new Promise(function(resolve, reject) {
    function attemp() {
      getData().then((data) => {
        resolve(data);
      }).catch((err) => {
        if (times === 0) {
          reject(err)
        } else {
          times--;
          setTimeout(attemp, delay);
        }
      })
    }
    attemp();
  })
}

Promise.finally

不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后边的then。

Promise.prototype.finally = function(cb) {
  return this.then((value) => {
    return Promise.resolve(cb()).then(() => {
      return value;
    });
  }, (errot) => {
    return Promise.resolve(cb()).then(() => {
      throw error
    })
  })
}

附加:

Promise的构造函数是同步执行的。then 中的方法是异步执行的。

Promise 是微任务,setTimeout 是宏任务,同一个事件循环中,promise.then总是先于 setTimeout 执行。


努力,只为遇见更好的自己!