| JavaScript对话面试官系列 | - 书写Promise

1,044 阅读10分钟

起因

当我输入了大量来自优质个人博客文章,经典书籍,名牌讲师的 JavaScript 系统知识之后,我反而变得有些困惑。我明明可以流利的书写八大继承,流利的书写 Underscore 库中的防抖节流,深拷贝……,我也知晓什么是闭包,什么是迭代器,生成器……。但是问题来了,当我试图将一个 JavaScript 核心概念,比如闭包……,介绍给同学,讲解给自己,对话面试官的时候。原本自认为可以脱口而出的语言,到嘴边却显得吞吞吐吐。只能用只言片语,或者东拼西凑的知识点来表达给对方。这无疑让我有了一种,虽然花了大量时间但是却从未拥有过 JavaScript 的感觉。

目的

所以这个系列要尝试解决的问题就是当别人询问或者考察我JavaScript 核心概念的时候,我可以尽可能流畅的,清晰的表达给他人。

期望

希望掘金优秀的前端技术人员和前辈们可以在百忙之中多多补充这篇文章,多多审查这篇文章,多多提问我。我期望可以通过这个系列来解决我当前的问题

你按照你的思路,来书写一个Promise

回答你的思路,根据思路书写Promise

  • 首先和面试官聊一聊我书写Promise的思路。我准备从三大方向出发去书写Promise
  • 第一个方向是根据Promise特征来确立Promise的类结构体。
    • 异步的Promise 是一个有三种状态的对象,pending(待定), fufilled(兑现), rejected(拒绝)
    • 无论落定为哪种状态都是不可逆的。
    • Promise 的状态转换为兑现,就会有一个兑现的值 value,转换为拒绝,就会有拒绝的原因 reason。
    • reson 和 value 会作为参数传递给 Promise 执行器函数当中的 resolve 和 reject 函数参数。
  • 根据特征书写出Promise类结构体。
/* Promise的三种状态 */
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_RESOLVE = "fulfilled";
const PROMISE_STATUS_REJECT = "reject";
class CyanPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    /* 兑现值 */
    this.value = value;
    /* 拒绝原因 */
    this.reason = reason;

    const resolve = (value) => {
      /* 状态不可逆 */
      if (this.status === PROMISE_STATUS_PENDING) {
        /* 异步Promise */
        queueMicrotask(() => {
          this.status = PROMISE_STATUS_RESOLVE;
          this.value = value;
        });
      }
    };
    const reject = (reason) => {
      /* 状态不可逆 */
      if(this.status === PROMISE_STATUS_PENDING){
        /* 异步Promise */
        queueMicrotask(() => {
          this.status = PROMISE_STATUS_REJECT;
          this.reason = reason;
         });
      }
      
    };
    executor(resolve, reject);
  }
}
  • 第二个方向是根据then方法的特征和作用去书写Promise的实例then方法。
    • then方法作用是为上一个promise实例添加兑现和拒绝状态处理程序。
    • then方法的特点是支持链式调用,多次调用,异步调用。
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_RESOLVE = "fulfilled";
const PROMISE_STATUS_REJECT = "reject";
class CyanPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    /* 兑现值 */
    this.value = undefined;
    /* 拒绝原因 */
    this.reason = undefined;
    /* 多次调用 -> 兑现处理队列 */
    this.onResolveFns = [];
    /* 多次调用 -> 拒绝处理队列 */
    this.onRejectFns = [];
    const resolve = (value) => {
      /* 状态不可逆 */
      if (this.status === PROMISE_STATUS_PENDING) {
        /* 异步Promise */
        queueMicrotask(() => {
          this.status = PROMISE_STATUS_RESOLVE;
          this.value = value;
          this.onResolveFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    const reject = (reason) => {
      /* 状态不可逆 */
      if (this.status === PROMISE_STATUS_PENDING) {
        /* 异步Promise */
        queueMicrotask(() => {
          this.status = PROMISE_STATUS_REJECT;
          this.reason = reason;
          this.onRejectFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  then(onFulfilled, onRejected) {
    /* 支持链式调用 */
    return new CyanPromise((resolve, reject) => {
      /* resolve是微任务,所以外部的promise实例(即前一个/第一个)进入then时仍然为pending状态 */
      if (this.status === PROMISE_STATUS_PENDING) {
        /* 多次调用,添加到对应处理队列 */
        this.onResolveFns.push((value) => {
          const result = onFulfilled(value);
          /* 处理当前的promise实例 */
          resolve(result);
        });
        this.onRejectFns.push((reason) => {
          const error = onRejected(reason);
          /* 处理当前的promise实例 */
          reject(error);
        });
      }
      /* 支持异步调用 */
      /* 异步处理程序当中调用then,resolve中微任务会先执行,所以外部的promise实例(即前一个/第一个)为resolve状态 */
      if (this.status === PROMISE_STATUS_RESOLVE) {
        const result = onFulfilled(this.value);
        resolve(result);
      }
      /* 支持异步调用 */
      /* 异步处理程序当中调用then,resolve中微任务会先执行,所以外部的promise实例(即前一个/第一个)为reject状态 */
      if (this.status === PROMISE_STATUS_REJECT) {
        const reason = onFulfilled(this.reason);
        reject(reason);
      }
    });
  }
}
  • 第三个方向是错误处理,公共逻辑代码提取和边缘判断
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_RESOLVE = "fulfilled";
const PROMISE_STATUS_REJECT = "reject";
class CyanPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    /* 兑现值 */
    this.value = undefined;
    /* 拒绝原因 */
    this.reason = undefined;
    /* 多次调用 -> 兑现处理队列 */
    this.onResolveFns = [];
    /* 多次调用 -> 拒绝处理队列 */
    this.onRejectFns = [];
    const resolve = (value) => {
      /* 状态不可逆 */
      if (this.status === PROMISE_STATUS_PENDING) {
        /* 异步Promise */
        queueMicrotask(() => {
          this.status = PROMISE_STATUS_RESOLVE;
          this.value = value;
          this.onResolveFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    const reject = (reason) => {
      /* 状态不可逆 */
      if (this.status === PROMISE_STATUS_PENDING) {
        /* 异步Promise */
        queueMicrotask(() => {
          this.status = PROMISE_STATUS_REJECT;
          this.reason = reason;
          this.onRejectFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    /* 错误处理 -> 执行器函数的错误处理 */
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
    /* 边缘处理, 防止onFulfilled 或者 onRejected 是undefined */
    const defalutOnRejected = (err) => {
      throw err;
    };
    const defalutOnResolve = (res) => res;
    onFulfilled = onFulfilled || defalutOnResolve;
    onRejected = onRejected || defalutOnRejected;
    /* 支持链式调用 */
    return new CyanPromise((resolve, reject) => {
      /* resolve是微任务,所以外部的promise实例(即前一个/第一个)为pending状态 */
      if (this.status === PROMISE_STATUS_PENDING) {
        /* 多次调用,添加到对应处理队列 */
        this.onResolveFns.push((value) => {
          /* 错误处理 -> 兑现处理程序的错误处理 */
          // try {
          //   const result = onFulfilled(value);
          //   /* 处理当前的promise实例 */
          //   resolve(result);
          // } catch (err) {
          //   reject(err);
          // }
          // 公共代码提取
          executorCatchError(onFulfilled, value, resolve, reject);
        });
        this.onRejectFns.push((reason) => {
          /* 错误处理 -> 拒绝处理程序的错误处理 */
          // try {
          //   const error = onRejected(reason);
          //   /* 处理当前的promise实例 */
          //   resolve(error);
          // } catch (err) {
          //   reject(error);
          // }
          // 公共代码提取
          executorCatchError(onRejected, reason, resolve, reject);
        });
      }
      /* 支持异步调用 */
      /* 异步处理程序当中调用then,resolve中微任务会先执行,所以外部的promise实例(即前一个/第一个)为resolve状态 */
      if (this.status === PROMISE_STATUS_RESOLVE) {
        // try {
        //   const result = onFulfilled(this.value);
        //   resolve(result);
        // } catch (err) {
        //   reject(err);
        // }
        // 公共代码提取
        executorCatchError(onFulfilled, this.value, resolve, reject);
      }
      /* 支持异步调用 */
      /* 异步处理程序当中调用then,resolve中微任务会先执行,所以外部的promise实例(即前一个/第一个)为reject状态 */
      if (this.status === PROMISE_STATUS_REJECT) {
        // try {
        //   const reason = onRejected(this.reason);
        //   resolve(reason);
        // } catch (err) {
        //   reject(err);
        // }
        // 公共代码提取
        executorCatchError(onRejected, this.reason, resolve, reject);
      }
    });
  }
}
function executorCatchError(onExecutorFn, allValue, resolve, reject) {
  try {
    const val = onExecutorFn(allValue);
    resolve(val);
  } catch (err) {
    reject(err);
  }
}

普通版完结

  • 到此为止,我们已经实现了一个简单版本的Promise,并且代码已经基本具备了Promise大部分基本功能。面试时如果可以将上面的部分写出来,已经算非常不错了。如果面试官深挖,就上咱们的Plus版本。
  • Plus版本,完善在then处理程序当中返回新的Promise,返回thenable对象的情况。

普通版总结

  • 一定打好JavaScript基础,如果this基础不牢靠,你甚至分不清楚代码中this指向的是哪一次的promise实例,是哪一次的处理兑现或者拒绝状态的队列,如果事件循环机制不牢靠,你甚至分不清楚resolve,then和支持异步调用后的执行顺序。这也是我最开始手写Promise的问题。
  • 抓住Promise特征,根据Promise的特征从创造者的角度去写Promise。
  • 抓住 then方法实现,一定一定要想清楚,此次的处理程序或者此次的处理程序队列要处理的是哪一次的promsie实例的兑现值或者拒绝值,是then方法返回的那一个Promise?还是外部的那一个Promise?

Plus版本开始

能不能将你的Promise改进为支持返回新的Promise对象或者thenable对象

  • 前两个方向代码不变,第三个方向代码发生变化
  • 第三个方向还是错误处理,公共逻辑代码提取和边缘判断
  • Plus版本,主要完善在then处理程序当中返回新的Promise对象,返回thenable对象的情况。
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_RESOLVE = "fulfilled";
const PROMISE_STATUS_REJECT = "reject";
class CyanPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    /* 兑现值 */
    this.value = undefined;
    /* 拒绝原因 */
    this.reason = undefined;
    /* 多次调用 -> 兑现处理队列 */
    this.onResolveFns = [];
    /* 多次调用 -> 拒绝处理队列 */
    this.onRejectFns = [];
    const resolve = (value) => {
      /* 状态不可逆 */
      if (this.status === PROMISE_STATUS_PENDING) {
        /* 异步Promise */
        queueMicrotask(() => {
          this.status = PROMISE_STATUS_RESOLVE;
          this.value = value;
          this.onResolveFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    const reject = (reason) => {
      /* 状态不可逆 */
      if (this.status === PROMISE_STATUS_PENDING) {
        /* 异步Promise */
        queueMicrotask(() => {
          this.status = PROMISE_STATUS_REJECT;
          this.reason = reason;
          this.onRejectFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    /* 错误处理 -> 执行器函数的错误处理 */
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
   /* 边缘处理, 防止onFulfilled 或者 onRejected 是undefined */
    const defalutOnRejected = (err) => {
      throw err;
    };
    const defalutOnResolve = (res) => res;
    onFulfilled = onFulfilled || defalutOnResolve;
    onRejected = onRejected || defalutOnRejected;
    /* 支持链式调用 */
    const currentPromise = new CyanPromise((resolve, reject) => {
      /* resolve是微任务,所以外部的promise实例(即前一个/第一个)为pending状态 */
      if (this.status == PROMISE_STATUS_PENDING) {
        /* 多次调用,添加到对应处理队列 */
        this.onResolveFns.push((value) => {
          /* 错误处理 -> 兑现处理程序的错误处理 */
          try {
            const result = onFulfilled(value);
            /* 处理当前返回promise实例的返回结果,返回结果可能是普通值,可能是新的promise对象,可能是thenable对象 */
       +    handleReturnResult(currentPromise, result, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
        this.onRejectFns.push((reason) => {
          /* 错误处理 -> 拒绝处理程序的错误处理 */
          try {
            const error = onRejected(reason);
            /* 处理当前返回promise实例的返回结果,返回结果可能是普通值,可能是新的promise对象,可能是thenable对象 */
       +    handleReturnResult(currentPromise, error, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }
      /* 支持异步调用 */
      /* 异步处理程序当中调用then,resolve中微任务会先执行,所以外部的promise实例(即前一个/第一个)为resolve状态 */
      if (this.status === PROMISE_STATUS_RESOLVE) {
        try {
          const result = onFulfilled(this.value);
          /* 处理当前返回promise实例的返回结果,返回结果可能是普通简单值,普通对象,可能是新的promise对象,可能是thenable对象 */
     +     handleReturnResult(currentPromise, result, resolve, reject);
        } catch (err) {
          reject(err);
        }
      }
      /* 支持异步调用 */
      /* 异步处理程序当中调用then,resolve中微任务会先执行,所以外部的promise实例(即前一个/第一个)为reject状态 */
      if (this.status === PROMISE_STATUS_REJECT) {
        try {
          const reason = onRejected(this.reason);
          /* 处理当前返回promise实例的返回结果,返回结果可能是普通值,可能是新的promise对象,可能是thenable对象 */
      +   handleReturnResult(currentPromise, reason, resolve, reject);
        } catch (err) {
          reject(err);
        }
      }
    });
    return currentPromise;
  }
}
function handleReturnResult(currentPromise, allValue, resolve, reject) {
  /* 如果返回的是当前的实例即本身 */
  if (currentPromise === allValue) {
    return reject(new TypeError("chaining cycle detected for promise"));
  }
  /* 如果返回的是新的promise实例对象 */
  if (allValue instanceof CyanPromise) {
    if (allValue.status === PROMISE_STATUS_PENDING) {
      /* 根据返回的新的Promise对象状态来处理返回值 */
      allValue.then(
        (res) => {
          resolve(res);
        },
        (err) => {
          reject(err);
        }
      );
    }
  } else if (allValue !== null && (typeof allValue === "object" || typeof allValue === "function")) {
    let then;
    /* 防止在thenable对象当中多次调用resolve,reject */
    let isCall = false;
    try {
      then = allValue.then;
    } catch (err) {
      reject(err);
    }
    /* 如果返回的是thenable对象 */
    if (typeof then ==== "function") {
      /* 传入闭包函数,去接收外界传入的兑现值或者拒绝理由,并且扩展拒绝多次调用功能 */
      then.call(
        allValue,
        (value) => {
          if (!isCall) {
            isCall = true;
            resolve(value);
          }
        },
        () => {
          if (!isCall) {
            isCall = true;
            reject(value);
          }
        }
      );
    } else {
      /* 否则说明返回的是普通对象 */
      resolve(allValue);
    }
  } else {
    /* 返回其他简单类型值,都作为下一次的兑现值 */
    resolve(allValue);
  }
}

你说一下 Promsie 有哪些方法,并且试着实现一下它们。

首先回答类方法resolvereject

  • 首先想和面试官聊一聊 Promise 的类方法 - resolvereject的功能和实现,对于 Promise.resolve(value)Promise.reject(reason)一样都是实例化一个期约。对于前者的期约转态是兑现,后者的期约状态是拒绝。

其次回答类方法raceall

  • 其次想和面试官聊一聊 Promise 的类方法 - raceall的功能和实现, Promise.all()Promise.race()作用一样都是将多个 Promise 实例,包装组合成一个新的 Promise 实例, Promise.all() 创建的期约会在一组期约全部解决之后再解决。Promise.race不会对解决或者拒绝的期约加以区分,只要是第一个落定的期约就会被包装成新的期约返回。

之后回答类方法anyallSettled

  • 之后想和面试官聊一聊 Promise 的类方法 - anyallSettled的功能和实现,Promise.allSettled()将一组 Promise 实例返回的所有结果,包装成一个新的 Promise 实例。Promise.any()接收一组 Promise实例,只要一个实例状态变为 fulfilled,包装实例就是 fulfilled 状态,如果所有参数都变成了 rejected 状态,包装实例才会变成 rejected状态。

最后回答原型方法catchfinally

  • 然后想和面试官聊一聊 Promise 的原型方法 - catchfinally的功能和实现Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
class MyPromise {
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
  finally(final) {
    this.then(final, final);
  }
  static resolve(value) {
    return new CyanPromise((resolve, reject) => {
      resolve(value);
    });
  }
  static reject(reson) {
    return new CyanPromise((resolve, reject) => {
      reject(reson);
    });
  }
  static race(promises) {
    return new Promise((resolve, reject) => {
      promises.forEach((p) => {
        p.then(resolve, reject);
      });
    });
  }
  static all(promises) {
    const result = [];
    const len = promises.length;
    return new Promise((resolve, reject) => {
      promises.forEach((p) => {
        p.then((res, index) => {
          result.push(res);
          if (result.length == len) {
            resolve(result);
          }
        }, reject);
      });
    });
  }
  static any(promises) {
    const reasons = [];
    const len = promises.length;
    return new Promise((resolve, reject) => {
      promises.forEach((p, index) => {
        p.then(resolve, (err) => {
          reasons.push(err);
          if (len === reasons.length) {
            reject(new AggregateError(reasons));
          }
        });
      });
    });
  }
  static allSettled(promises) {
    const result = [];
    const len = promises.length;
    return new Promise((resolve, reject) => {
      promises.forEach((p, index) => {
        p.then(
          (value) => {
            result.push({ status: "fulfilled", value });
            if (result.length === len) {
              resolve(result);
            }
          },
          (reason) => {
            result.push({ status: "rejected", reason });
            if (result.length === len) {
              resolve(result);
            }
          }
        );
      });
    });
  }
}