返回

深入揭秘Promise实现:从经典面试题到V8源码剖析

前端

前言:一道让我失眠的Promise面试题

在一次面试中,面试官抛出了一个关于Promise的问题,让我当场失眠了。问题是这样的:

假设我们有一个Promise对象,然后我们调用它的then()方法,在这个then()方法中,我们又返回了一个新的Promise对象。请问,当第一个Promise对象resolve()时,第二个Promise对象的状态会是什么?

我当时想了很久,但就是答不上来。因为Promise的实现细节实在太复杂了,我无法在短时间内理清其中的逻辑关系。后来,我花了几天时间仔细研究了Promise的V8源码,终于搞清楚了它的工作原理。现在,我就把我的研究成果分享给大家。

Promise的实现原理

Promise是一个异步编程的利器,它可以帮助我们处理异步操作,让代码更加简洁和易于维护。Promise的实现原理并不复杂,但它涉及到JavaScript引擎内部的一些细节。

在V8 JavaScript引擎中,Promise是由一个叫做PromiseConstructor的函数实现的。这个函数有一个参数,就是Promise的执行器(executor)。执行器是一个函数,它接受两个参数:resolvereject。当Promise的状态变为resolved(已完成)时,调用resolve函数;当Promise的状态变为rejected(已拒绝)时,调用reject函数。

Promise的初始状态是pending(等待)。当执行器被调用时,Promise的状态会发生改变。如果执行器调用了resolve函数,则Promise的状态变为resolved,如果执行器调用了reject函数,则Promise的状态变为rejected。

Promise的状态一旦发生改变,就会触发Promise的回调函数。回调函数可以是.then()方法或.catch()方法。.then()方法处理resolved状态的Promise,.catch()方法处理rejected状态的Promise。

深入剖析Promise源码

为了更好地理解Promise的实现原理,我们来看一下它的V8源码。Promise的源码位于src/builtins/promise.js文件中。这个文件非常长,但其中有几段代码非常重要,我们重点来看一下。

Promise构造函数

Promise的构造函数如下:

function PromiseConstructor(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('Promise resolver ' + executor + ' is not a function');
  }
  this.state_ = PromiseState.kPending;
  this.value_ = undefined;
  this.queue_ = [];
  executor.call(this, this.resolve_.bind(this), this.reject_.bind(this));
}

这个函数接收一个参数executorexecutor是一个函数,它接受两个参数:resolvereject。当Promise的状态变为resolved(已完成)时,调用resolve函数;当Promise的状态变为rejected(已拒绝)时,调用reject函数。

Promise的状态

Promise的初始状态是pending(等待)。当执行器被调用时,Promise的状态会发生改变。如果执行器调用了resolve函数,则Promise的状态变为resolved,如果执行器调用了reject函数,则Promise的状态变为rejected。

Promise的状态可以用枚举值来表示,如下:

enum PromiseState {
  kPending,
  kResolved,
  kRejected,
};

Promise的回调队列

Promise有一个回调队列(queue_),用来存储.then().catch()方法的回调函数。当Promise的状态发生改变时,就会触发队列中的回调函数。

Promise的解析函数

Promise的解析函数如下:

resolve_(value) {
  if (this.state_ !== PromiseState.kPending) {
    return;
  }
  this.state_ = PromiseState.kResolved;
  this.value_ = value;
  this.queue_.forEach((callback) => {
    callback.onFulfilled(value);
  });
}

这个函数接收一个参数value,它是Promise的resolved值。当Promise的状态变为resolved时,这个函数会被调用。该函数会将Promise的状态设置为resolved,并将resolved值存储到value_属性中。然后,它会遍历回调队列,并调用每个回调函数的onFulfilled()方法,并将resolved值作为参数传递给onFulfilled()方法。

Promise的拒绝函数

Promise的拒绝函数如下:

reject_(reason) {
  if (this.state_ !== PromiseState.kPending) {
    return;
  }
  this.state_ = PromiseState.kRejected;
  this.value_ = reason;
  this.queue_.forEach((callback) => {
    callback.onRejected(reason);
  });
}

这个函数接收一个参数reason,它是Promise的rejected原因。当Promise的状态变为rejected时,这个函数会被调用。该函数会将Promise的状态设置为rejected,并将rejected原因存储到value_属性中。然后,它会遍历回调队列,并调用每个回调函数的onRejected()方法,并将rejected原因作为参数传递给onRejected()方法。

找到两次微任务的创建位置

在上一篇分析文章中,我们没有找到Promise创建微任务的具体位置。现在,我们已经对Promise的实现原理有了更深入的了解,我们就可以找到微任务的创建位置了。

Promise创建微任务的位置有两个:

  1. 当Promise的状态从pending变为resolved时,会创建一个微任务来执行.then()方法的回调函数。
  2. 当Promise的状态从pending变为rejected时,会创建一个微任务来执行.catch()方法的回调函数。

微任务是JavaScript引擎内部的一种任务队列,它比宏任务(如定时器和事件处理程序)具有更高的优先级。这意味着,当微任务队列中有任务时,JavaScript引擎会优先执行微任务,然后才会执行宏任务。

结束语

通过对Promise V8源码的分析,我们对Promise的实现原理有了更深入的理解。我们还找到了Promise创建微任务的具体位置。这些知识对于我们编写出更健壮和可维护的JavaScript代码非常重要。

我希望这篇文章能帮助大家更好地理解Promise的实现原理,并能帮助大家编写出更好的JavaScript代码。