Promise 实现基础篇

668 阅读10分钟

Promise 本身是一个状态机,每一个 Promise 实例只能有三个状态 pendingfulfilled(resolved)rejected,并且状态之间的转换只能pendingfulfilledpendingrejected,状态变化是不可逆的。

Promise 拥有一个 then 方法,这个方法可以被调用多次,并且返回一个 Promise 对象。

Promise 支持链式调用,内部保存有一个 value 值,用来保存上次执行的结果值;如果有报错,就说明保存的是异常信息。

Promise 的实现步骤

遵循 Promise/A+ 规范。

  1. 实现 Promise 的基本框架
  2. 增加状态机
  3. 实现 then 方法
  4. 实现异步调用
  5. 实现 then 的链式调用
  6. 实现 catch 的异常处理

1. 实现 Promise 的基本框架结构

  • 使用 new 的方式得到一个 promise 实例化对象,接收一个函数为参数
    • 该函数接收两个函数作为参数,resolvereject,代表着我们需要改变当前实例的状态到 已完成 或是 已拒绝

    • 该函数在实例化过程中,会自执行,已供外部可以调用所提供的回调函数。

      • promise 必须是一个对象或函数。并具有 then 方法。
      • thenable (就是拥有 .then() 的对象)
  • 依据 promise 必须处于三种状态之一:等待中已完成已拒绝
    • 状态的变更只能从等待中 过度到 已完成已拒绝
    • 状态扭转后,是不可变的。
    • 在已完成状态下,返回的 value 值必须一个 JavaScript 的合法值(包含 undefinedthenablepromise)。
    • 在拒绝状态下,返回一个 reason 值,代表拒绝原因。
const PENDING   = 'pending',
      FULFILLED = 'fulfilled',
      REJECTED  = 'rejected';

class MyPromise {
  constructor(executor) {
    this.status = PENDING;
    this.value  = undefined;
    this.reason = undefined;

    // 提供给予外部回调函数(resolve、reject),来修改当前 promise 状态
    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);
  }
  then() {}

  catch() {}

  static resolve () {}

  static reject() {}

  static all () {}

  static race() {}
}

module.exports = MyPromise;

大致依据这些要求,一个 Promise 大致的架子就出来。然后针对 Promises/A+ 规范的解决流程依依实现其功能。

2. 实现 then 方法

  • 通过提供的 then 方法,获取当前 promise 的值或原因。

  • then 必须接收两个参数(可选):onFulfilledonRejected

    • 两个参数必须是一个函数,如果不是就忽略。
  • onFulfilled是函数。

    • onFulfilled 必须在 promise 实例对象为「已完成」后调用它,以 promise 的值(value)作为此函数的参数。
    • 不能在 promise 状态为「已完成」之前调用它。
    • 不能被多次调用。
  • onRejected是函数。

    • onRejected 必须在 promise 实例对象为「已拒绝」后调用它,以 promise 的原因(reason)作为此函数的参数。
    • 不能在 promise 状态为「已拒绝」之前调用它。
    • 不能被多次调用。
  • onFulfilledonRejected 必须作为函数调用(没有 this)。

    虽说没有 this 但非要使用的话:

    • 在严格模式下,this 指向 undefined
    • 非严格模式下,指向全局对象。
class MyPromise {
  // 省略无关代码...
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw new Error(reason)
    };
    let isCalled = false; // 防止被重复调用

    if (this.status === FULFILLED) {
      if (isCalled) return
      isCalled = true;
      onFulfilled(this.value)
    }
    if (this.status === REJECTED) {
      if (isCalled) return
      isCalled = true;
      onRejected(this.reason)
    }
  }
}

以上代码对于同步代码还行,与异步就不行了,如下:

const MyPromise = require("./src/MyPromise2");

const promise1 = new MyPromise((resolve, reject) => {
  // resolve('success')
  setTimeout(() => {
    reject('fail')
  }, 1000);
})
.then(
  value => {
    console.log("🚀 ~ file: index.js ~ line 38 ~ value", value)
  },
  reason => {
    console.log("🚀 ~ file: index.js ~ line 39 ~ reason", reason)
  }
)

异步执行之前 then 方法已经就执行完了,所以当时的 status 还处于在 等待中 ,并不知道后续状态已经变更。这里我们还得处理下:

  • 当状态为 pending 时,将接收的回调函数加入队列中
  • 通过本身 promise 提供给外部的回调函数,去触发队列收集中的回调函数
const PENDING   = 'pending',
      FULFILLED = 'fulfilled',
      REJECTED  = 'rejected';

class MyPromise {
  constructor(executor) {
    this.status = PENDING;
    this.value  = undefined;
    this.reason = undefined;

    // 针对异步回调,进行收集
    this.onFulfilledList = [];
    this.onRejectedList = []

    // 提供给予外部回调函数(resolve、reject),来修改当前 promise 状态
    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 发布
        this.onFulfilledList.forEach(fn => fn())
      }
    }
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 发布
        this.onRejectedList.forEach(fn => fn())
      }
    }
    // 执行,已供外部可以拿到回调函数
		executor(resolve, reject);
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw new Error(reason)
    };
    let isCalled = false; // 防止被重复调用

    if (this.status === FULFILLED) {
      if (isCalled) return
      isCalled = true;
      onFulfilled(this.value)
    }

    if (this.status === REJECTED) {
      if (isCalled) return
      isCalled = true;
      onRejected(this.reason)
    }

    if (this.status === PENDING) {
      // 订阅
      this.onFulfilledList.push(() => onFulfilled(this.value));
      this.onRejectedList.push(() => onRejected(this.reason));
    }
  }
}

module.exports = MyPromise;

再来测试下之前的例子,对于异步地处理就正常了。

  • 在执行上下文堆栈仅包含平台代码之前,不得调用 onFulfilledonRejected

这里的“平台代码”是指引擎,环境和 promise 实现的代码。实际上,此要求可确保在调用事件循环之后,使用新堆栈异步执行 onFulfilledonRejected。可以使用 “宏任务”机制(如 setTimeoutsetImmediate)或 “微任务”机制(如 MutationObserverprocess.nextTick)来实现。由于promise实现被视为平台代码,因此它本身可能包含**任务调度队列 (task-scheduling queue)**或“trampoline”,在其中调用处理程序。

也就是说 onFulfilledonRejected 函数的调用不能再当前执行上下堆栈上,通过上述说明,我们来利用 setTimeoutonFulfilledonRejected 的执行塞入异步队列里。

class MyPromise {
  // 省略无关代码...
    then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw new Error(reason)
    };
    let isCalled = false; // 防止被重复调用

    if (this.status === FULFILLED) {
      if (isCalled) return
      isCalled = true;
      setTimeout(() => {
        onFulfilled(this.value)
      }, 0)
    }

    if (this.status === REJECTED) {
      if (isCalled) return
      isCalled = true;
      setTimeout(() => {
        onRejected(this.reason)
      }, 0)
    }

    if (this.status === PENDING) {
      // 订阅
      this.onFulfilledList.push(() => {
        setTimeout(() => {
          onFulfilled(this.value)
        }, 0);
      });
      this.onRejectedList.push(() => {
        setTimeout(() => {
          onRejected(this.reason)
        }, 0);
      });
    }
  }
}

3. 实现 Promise 链式调用

  • then 方法可能会在同一个 promise 中被多次使用,支持链式调用。

    • promise 为「已完成」状态,所有的 onFulfilled 回调函数必须按照 then方法调用顺序执行。
    • promise 为「已拒绝」状态,所有的 onRejected 回调函数必须按照 then 方法调用的顺序执行。
  • then 必须返回一个 promise

    promise2 = promise1.then(onFulfilled, onRejected)
    
    • 如果 onFulfilledonRejected 返回的是一个值( x ), 就执行 [[Resolve]](promise2, x) ([[Resolve]](promise2, x) 调用携带 promise2x 作为参数的函数) 这个函数会执行上一次返回来的 promise
    • 如果 onFulfilledonRejected 抛出一个异常, promise 的状态为 「已拒绝」,这个异常将为拒绝原因返回。
    • 如果 onFulfilled 不是一个函数,并且 promise1 为「已完成」状态,那么 promise2 的状态也为「已完成」并且以 promise1 的值作为返回。
    • 如果 onRejeceted 不是一个函数,并且 promise1 为 「已拒绝」状态,那么 promise1 的拒绝原因也作为 promise2 的拒绝原因。

观看 Promises/A+ 规范, 在实现链式调用的时候,通过一个 [[Resolve]](promise2, x) 的函数来实现完成的。

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

/**
 * promise 解析流程操作
 * @param {Object} promise2 promise 实例化对象
 * @param {*} x 值,传递过来回调函数的值 (普通的 JavaScript 值,thenable、promise、error)
 * @param {*} resolve TODO: 因为目前 MyPromise 本身还没有实现 resolve 和 reject 的静态方法,所以暂时先传递进来
 * @param {*} reject
 */
function resolutionProcedure (promise2, x, resolve, reject) {

}

class MyPromise {
	// 省略无关代码...
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw new Error(reason)
    };
    let isCalled = false; // 防止被重复调用
    const promise2 = new MyPromise((resolve, reject) => {

      if (this.status === FULFILLED) {
        if (isCalled) return
        isCalled = true;
        setTimeout(() => {
          let x = onFulfilled(this.value)
          resolutionProcedure(promise2, x, resolve, reject)
        }, 0)
      }

      if (this.status === REJECTED) {
        if (isCalled) return
        isCalled = true;
        setTimeout(() => {
          let x = onRejected(this.reason)
          resolutionProcedure(promise2, x, resolve, reject)
        }, 0)
      }

      if (this.status === PENDING) {
        // 订阅
        this.onFulfilledList.push(() => {
          setTimeout(() => {
            let x = onFulfilled(this.value)
            resolutionProcedure(promise2, x, resolve, reject)
          }, 0);
        });
        this.onRejectedList.push(() => {
          setTimeout(() => {
            let x = onRejected(this.reason)
            resolutionProcedure(promise2, x, resolve, reject)
          }, 0);
        });
      }
    })

    return promise2;
  }
}

定义好 resolutionProcedure 我们就来实现它:

  • 如果 promisex 引用相同的对象,状态为「已拒绝」并返回 TypeError 作为原因拒绝 promise

  • 如果 x 是一个 promise ,就采用 x 的状态。

    • 如果 x 是「等待中」状态,则 promise 保持为「等待中」,直到 x 处理完为「已完成」或 「已拒绝」
    • 如果 x 为「已完成」,则 x 的值作为 promise 的值。
    • 如果 x为「已拒绝」,则 x 的原因作为 promise 的原因。
  • 如果 x 是一个 objectfunction

    • x 存在 then 方法

    首先存储对 x.then的引用,然后测试该引用,然后调用该引用的过程避免了对x.then属性的多次访问。此类预防措施对于确保访问者属性的一致性非常重要,因为访问者属性的值在两次检索之间可能会发生变化。

    • 如果在检索 x.then属性时抛出一个异常,状态扭转「已拒绝」并以这个异常作为原因拒绝 promise

    • 如果 x.then() 是一个函数,则以 x 作为其 this 指向,以 resovlePromise 为第一个参数,rejectPromise 作为第二个参数传入。

      • 传递 y 值调用 resolvePromise,去执行 [[Resolve]](promise, y)
      • 传递 r 原因调用 rejectPromise, 状态为 「已拒绝」并以 r 作为原因拒绝promise
      • 如果同时调用 resolvePromiserejectPromise ,或者对同一参数进行了多次调用,则第一个调用具有优先权,而任何其他调用将被忽略。
      • 如果调用 x.then 时抛出一个异常
        • resolvePromiserejectPromise 已经被调用,则忽略它。
        • 否则以这个异常为原因拒绝 promise
    • 如果 x.then 不是一个函数,则以 x 为值实现 promise

  • 如果 x 即不是 object 也不是 function ,则以 x 为值实现 promise

function resolutionProcedure (promise2, x, resolve, reject) {
  if(promise2 === x) {
    // 通过 reject 抛出去时,被捕获时以是一个 String,不会立即提示,而需要下一个 then 时才能被抛出, 所以在这里直接进行抛出捕获
    try {
      throw new TypeError('TypeError: Chaining cycle detected for promise #<MyPromise>')
    } catch (error) {
      console.log(error)
      reject(error)
    }
  }
  let isCalled = false;

  // 如果 x 是一个对象、函数、promise, 将执行 then 方法
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    // x 是否存在 then 方法
    try {
      let then = x.then; // 先保存其引用

      if (typeof then === 'function') {
        if (isCalled) return
        isCalled = true

        then.call(x,
          (y) => {
            resolutionProcedure(promise2, y, resolve, reject)
          },
          (r) => {
            reject(r)
          }
        )
      } else {
        // 不是一个函数
        resolve(x)
      }
    } catch (error) {
      reject(error)
    }
  } else {
    // 普通值
    resolve(x)
  }
}

4. 实现 catch 的异常处理

将程序中的一些需要异常处理的地方加上 try...catch ,并实现 catch 实例化方法(特殊的 then 方法)。

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

/**
 * promise 解析流程操作
 * @param {Object} promise2 promise 实例化对象
 * @param {*} x 值,传递过来回调函数的值 (普通的 JavaScript 值,thenable、promise、error)
 * @param {*} resolve TODO: 因为目前 MyPromise 本身还没有实现 resolve 和 reject 的静态方法,所以暂时先传递进来
 * @param {*} reject
 */
function resolutionProcedure (promise2, x, resolve, reject) {
  if(promise2 === x) {
    // 通过 reject 抛出去时,被捕获时以是一个 String,不会立即提示,而需要下一个 then 时才能被抛出, 所以在这里直接进行抛出捕获
    try {
      throw new TypeError('TypeError: Chaining cycle detected for promise #<MyPromise>')
    } catch (error) {
      console.log(error)
      reject(error)
    }
  }
  let isCalled = false;

  // 如果 x 是一个对象、函数、promise, 将执行 then 方法
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    // x 是否存在 then 方法
    try {
      let then = x.then; // 先保存其引用

      if (typeof then === 'function') {
        if (isCalled) return
        isCalled = true

        then.call(x,
          (y) => {
            resolutionProcedure(promise2, y, resolve, reject)
          },
          (r) => {
            reject(r)
          }
        )
      } else {
        // 不是一个函数
        resolve(x)
      }
    } catch (error) {
      reject(error)
    }
  } else {
    // 普通值
    resolve(x)
  }
}

class MyPromise {
  constructor(executor) {
    this.status = PENDING;
    this.value  = undefined;
    this.reason = undefined;

    // 针对异步回调,进行收集
    this.onFulfilledList = [];
    this.onRejectedList = []

    // 提供给予外部回调函数(resolve、reject),来修改当前 promise 状态
    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 发布
        this.onFulfilledList.forEach(fn => fn())
      }
    }
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 发布
        this.onRejectedList.forEach(fn => fn())
      }
    }
    // 执行,已供外部可以拿到回调函数
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw new Error(reason)
    };
    let isCalled = false; // 防止被重复调用
    const promise2 = new MyPromise((resolve, reject) => {

      if (this.status === FULFILLED) {
        if (isCalled) return
        isCalled = true;
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            resolutionProcedure(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }

      if (this.status === REJECTED) {
        if (isCalled) return
        isCalled = true;
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolutionProcedure(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }

      if (this.status === PENDING) {
        // 订阅
        this.onFulfilledList.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value)
              resolutionProcedure(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0);
        });
        this.onRejectedList.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              resolutionProcedure(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0);
        });
      }
    })

    return promise2;
  }
	
  // 特殊的 then, 只执行 onRejected 回调
  catch() {
    return this.then(undefined, onRejected);
  }

  static resolve () {}

  static reject() {}

  static all () {}

  static race() {}
}

module.exports = MyPromise;