手把手实现 promise

164 阅读7分钟

本篇文章主要在于探究 Promise 的实现原理,带领大家一步一步实现一个 Promise , 不对其用法做说明,如果读者还对 Promise 的用法不了解,可以查看阮一峰老师的教程ES6 Promise.

Promise 基本结构

首先,我们要知道:

  • Promise 是一个类
  • new Promise 时,会返回一个 promise 的对象,它会传一个执行器(executor),这个执行器是立即执行的
  • 另外每个 promise 实例上都会有一个 then 方法,参数分别是成功(有成功的值)和失败(有失败的原用)两个方法
  • promise 有三个状态:成功态,失败态,等待态。
  • 默认状态是等待态,等待态可以变成成功态或失败态
  • 一旦成功或失败就不能再变会其他状态了
class Promise {
  constructor(executor) {
    //executor执行器
    this.status = "pending"; //默认等待状态
    this.value = undefined; //成功的值
    this.reason = undefined; //失败的原用

    let resolve = (value) => {
      if (this.status === "pending") {
        this.status = "resolved"; //成功
        this.value = value;
      }
    };
    let reject = (reason) => {
      if (this.status === "pending") {
        this.status = "rejected"; //失败
        this.reason = reason;
      }
    };
    executor(resolve, reject); //默认上执行器执行
  }
  then(onFufilled, onRejected) {
    if (this.status === "resolved") {
      //成功态
      onFufilled(this.value);
    }
    if (this.status === "rejected") {
      //失败态
      onRejected(this.reason);
    }
  }
}
module.exports = Promise;

以上,我们就简单的实现了一个同步的 promise。 测试一下吧

let Promise = require("./myPromise.js");
let promise = new Promise((resolve, reject) => {
  resolve("hello");
});
promise.then(
  (data) => {
    console.log(data);
  },
  (err) => {
    console.log(err);
  }
);

但是,我们知道,promise 主要解决的是异步回调问题。所以,异步调用必须实现起来。

异步调用的实现

当异步调用时,当调用实例的 then 时,状态可能还处于 pending 状态,这时我们需要在实例上定义两个存放成功和失败方法的数组,把需要执行的方法分别放到对应的数组里,等到异步时间到达的时候,再去执行对应数组里的方法。

class Promise {
  constructor(executor) {
    //executor执行器
    this.status = "pending"; //默认等待状态
    this.value = undefined; //成功的值
    this.reason = undefined; //失败的原用
    //存放then成功,失败的回调的数组
    this.onResovleCallbacks = [];
    this.onRejectedCallbacks = [];

    let resolve = (value) => {
      if (this.status === "pending") {
        this.status = "resolved"; //成功
        this.value = value;
        this.onResovleCallbacks.forEach((fn) => fn());
      }
    };
    let reject = (reason) => {
      if (this.status === "pending") {
        this.status = "rejected"; //失败
        this.reason = reason;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };
    executor(resolve, reject); //默认上执行器执行
  }
  then(onFufilled, onRejected) {
    if (this.status === "resolved") {
      //成功态
      onFufilled(this.value);
    }
    if (this.status === "rejected") {
      //失败态
      onRejected(this.reason);
    }
    if (this.status === "pending") {
      this.onResovleCallbacks.push(() => {
        onFufilled(this.value);
      });
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason);
      });
    }
  }
}

module.exports = Promise;

以上,我们就实现了 promise 的异步调用。 测试一下吧

let Promise = require("./myPromise.js");
let promise = new Promise((resolve, reject) => {
  setTimeout(function () {
    resolve("hello");
  }, 100);
});
promise.then(
  (data) => {
    console.log(data);
  },
  (err) => {
    console.log(err);
  }
);

以上,我们就实现了 promise 的异步调用。 测试一下吧

非正常执行处理

当执行的时候抛出异常时,我们应该让它当状态变为 rejected,去执行 then 的错误方法。 这时候,需要在执行器执行的时候 捕获一下错误,并作出 rejected 处理

try {
  executor(resolve, reject);
} catch (e) {
  //捕获到异常时,直接走失败
  reject(e);
}

链式调用的实现

说到链式调用,咱们接触最多的就是 jquery,jquery 实现链式调用是靠的是返回 this,promise 实现链式调用是不是也返回 this 呢?答案是,NO !它实现链式调用靠的是返回一个新的 promise。在 then 方法里,无论 promise 处于哪种状态,执行完后,都返回一个新的 promise。

then(onFufilled, onRejected) {
  let promise2; //返回的新promise
  promise2 = new Promise((resolve, reject) => {
    if (this.status === 'resolved') {
      onFufilled(this.value);
    }
    if (this.status === 'rejected') {
      onRejected(this.reason);
    }
    if (this.status === 'pending') {
      this.onResovleCallbacks.push(() => {
        onFufilled(this.value)
      });
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason)
      })
    }
  });
  return promise2;
}

链式调用之错误情况

在 then 中,无论是成功的回调还是失败的回调,只要返回了结果就会走下一个 then 中的成功,如果有错误,就会走下一个 then 的失败回调。即:下一个 then 的状态跟上一个 then 执行时候的状态无关。所以,在 then 执行的时候,onFufilled, onRejected 可能会出错,这时候,我们需要捕获错误,并处理成失败

promise2 = new Promise((resolve, reject) => {
  if (this.status === "resolved") {
    try {
      onFufilled(this.value);
    } catch (e) {
      reject(e);
    }
  }
  if (this.status === "rejected") {
    try {
      onRejected(this.reason);
    } catch (e) {
      reject(e);
    }
  }
  if (this.status === "pending") {
    this.onResovleCallbacks.push(() => {
      try {
        onFufilled(this.value);
      } catch (e) {
        reject(e);
      }
    });
    this.onRejectedCallbacks.push(() => {
      try {
        onRejected(this.reason);
      } catch (e) {
        reject(e);
      }
    });
  }
});

链式调用之兼容多种情况

  • 如果第一个 promise 返回一个普通值,直接将这个返回值,传递给下一次 then 的 resolve。
  • 如果第一个 promise 返回一个 promise,需要等待返回的这个 promise 执行后的结果,传给下一次 then 处理第一次 promise 执行后的返回值 x,then 方法的每个状态都需要处理一下
try {
  //x是上一个promise返回值,可能是一个普通值,也可能是一个promise;x也可能是别人的promise,我们可以写一个方法,统一处理
  let x = onFufilled(this.value);
  //入参:下一次then的实例promise2,这次返回值x,promise2的成功方法,promise2的失败方法
  resolvePromise(promise2, x, resolve, reject);
} catch (e) {
  reject(e);
}

下面来实现 resolvePromise,用来处理多套 promise 共用的情况:

/*
 * resolvePromise
 * @Parameters
 * promise2:下一次then的实例promise2
 * x:这次返回值x
 * resolve:promise2的成功方法
 * reject:promise2的失败方法
 */
function resolvePromise(promise2, x, resolve, reject) {
  //x可能是别人的promise,所以尽可能的允许别人瞎写
  if (promise2 === x) {
    //返回的结果和promise是同一个,那么永远不会成功
    return reject(new TypeError("循环引用"));
  }
  let called; //是否调用过成功或失败
  // 看x是不是promise。promise应该是一个对象
  if (x != null && (typeof x === "object" || typeof x === "function")) {
    //可能是promise
    try {
      let then = x.then; // 如果是对象 就试着取一下then方法 如果有then,认为它是promise
      if (typeof then === "function") {
        // 如果then是函数,是promise
        then.call(
          x,
          (y) => {
            // 成功和失败只能调用一个
            if (called) return;
            called = true;
            // resolve的结果依旧是promise 那就继续解析
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r); // 失败了就失败了
          }
        );
      } else {
        resolve(x); // 直接成功即可
      }
    } catch (e) {
      // 取then出错了那就不要在继续执行了
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    //普通值 让promise2直接变成成功态
    resolve(x);
  }
}

以上,我们的 promise 好像已经差不多了,但是还有一个问题,需要处理。源码可以在 hen 中实现什么都不传。promise 中管这种现象叫,值的穿透。 因此,我们需要在 then 方法里,对 then 方法的入参进行容错处理:

onFufilled = typeof onFufilled === "function" ? onFufilled : (value) => value;
onRejected =
  typeof onRejected === "function"
    ? onRejected
    : (err) => {
        throw err;
      };

另外,promise 规范中要求,所有的 onFufilled 和 onRejected 都需要异步执行,如果不加异步可能造成测试的不稳定性,所以我们给执行这两个方法执行的地方都加上异步方法。

if (this.status === "resolved") {
  setTimeout(() => {
    try {
      let x = onFufilled(this.value);
      resolvePromise(promise2, x, resolve, reject);
    } catch (e) {
      reject(e);
    }
  }, 0);
}

完整

class Promise {
  constructor(executor) {
    //executor执行器
    this.status = "pending"; //默认等待状态
    this.value = undefined; //成功的值
    this.reason = undefined; //失败的原用
    this.onResovleCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = (value) => {
      if (this.status === "pending") {
        this.status = "resolved"; //成功
        this.value = value;
        this.onResovleCallbacks.forEach((fn) => fn());
      }
    };
    let reject = (reason) => {
      if (this.status === "pending") {
        this.status = "rejected"; //失败
        this.reason = reason;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };
    try {
      executor(resolve, reject); //默认上执行器执行
    } catch (e) {
      //捕获到异常时,直接走失败
      reject(e);
    }
  }
  then(onFufilled, onRejected) {
    onFufilled =
      typeof onFufilled === "function" ? onFufilled : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (err) => {
            throw err;
          };
    function resolvePromise(promise2, x, resolve, reject) {
      //x可能是别人的promise,所以尽可能的允许别人瞎写
      if (promise2 === x) {
        //返回的结果和promise是同一个,那么永远不会成功
        return reject(new TypeError("循环引用"));
      }
      //
      let called;
      // 看x是不是promise。promise应该是一个对象
      if (x != null && (typeof x === "object" || typeof x === "function")) {
        //可能是promise
        try {
          let then = x.then; // 如果是对象 我就试着取一下then方法 如果有then,认为它是promise
          if (typeof then === "function") {
            // then是函数,是promise
            then.call(
              x,
              (y) => {
                // 成功和失败只能调用一个
                if (called) return;
                called = true;
                // resolve的结果依旧是promise 那就继续解析
                resolvePromise(promise2, y, resolve, reject);
              },
              (r) => {
                if (called) return;
                called = true;
                reject(r); // 失败了就失败了
              }
            );
          } else {
            resolve(x); // 直接成功即可
          }
        } catch (e) {
          // 取then出错了那就不要在继续执行了
          if (called) return;
          called = true;
          reject(e);
        }
      } else {
        //普通值 让promise2直接变成成功态
        resolve(x);
      }
    }
    let promise2; //返回的新promise
    promise2 = new Promise((resolve, reject) => {
      if (this.status === "resolved") {
        setTimeout(() => {
          try {
            let x = onFufilled(this.value); //x是上一个promise返回值,可能是一个普通值,也可能是一个promise;x也可能是别人的promise,我们可以写一个方法,统一处理
            resolvePromise(promise2, x, resolve, reject); //下一次then的实例promise2,这次返回值x,promise2的成功方法,promise2的失败方法
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      if (this.status === "rejected") {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      if (this.status === "pending") {
        this.onResovleCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFufilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });
    return promise2;
  }
}
module.exports = Promise;