美文网首页
从头开始编写自己的Node.js Promise库

从头开始编写自己的Node.js Promise库

作者: 魂斗驴 | 来源:发表于2021-04-04 09:15 被阅读0次

Promise是JavaScript中首选的异步原语。回调变得越来越不常见,尤其是在Node.js中可以使用async/await的情况下。async/await是基于promise的,所以您需要了解promise以掌握async/await。在本文中,我将引导您编写自己的Promise库,并演示如何在async/await中使用它。

什么是promise?

在ES6规范中,promise是一个类,其构造executor函数带有一个函数。Promise类的实例具有一个then()函数。根据规范,Promise具有其他几个属性,但是出于本教程的目的,您可以忽略它们。下面是一个简单MyPromise类的脚手架。

class MyPromise {
  //`executor`具有两个参数,分别为`resolve()`和`reject()`。
  //executor函数负责调用`resolve()`或`reject()`表示
  //异步操作成功(已解决)或失败(已拒绝)。
  constructor(executor) {}

   //如果实现了promise,则会调用onFulfilled
   // 如果promise被拒绝, onRejected会被调用
  then(onFulfilled, onRejected) {}
}

executor函数有2个参数,resolve()reject()。一个promise是一个具有3个状态的状态:

  • pending:初始状态,表示尚未兑现promise
  • fulfilled:基础操作成功且具有关联值
  • rejected:基础操作失败,并且promise存在关联的错误

考虑到这一点,实现MyPromise构造函数的第一次迭代很简单。

constructor(executor) {
  if (typeof executor !== 'function') {
    throw new Error('Executor must be a function');
  }

   //内部状态。 $state是承诺的状态,而$chained是
   //实现promise后,我们需要调用的函数数组。
  this.$state = 'PENDING';
  this.$chained = [];

  // Implement `resolve()` and `reject()` for the executor function to use
  const resolve = res => {
    // A promise is considered "settled" when it is no longer
    // pending, that is, when either `resolve()` or `reject()`
    // was called once. Calling `resolve()` or `reject()` twice
    // or calling `reject()` after `resolve()` was already called
    // are no-ops.
    if (this.$state !== 'PENDING') {
      return;
    }
    // There's a subtle difference between 'fulfilled' and 'resolved'
    // that you'll learn about later.
    this.$state = 'FULFILLED';
    this.$internalValue = res;
    // If somebody called `.then()` while this promise was pending, need
    // to call their `onFulfilled()` function
    for (const { onFulfilled } of this.$chained) {
      onFulfilled(res);
    }
  };
  const reject = err => {
    if (this.$state !== 'PENDING') {
      return;
    }
    this.$state = 'REJECTED';
    this.$internalValue = err;
    for (const { onRejected } of this.$chained) {
      onRejected(err);
    }
  };

  // Call the executor function with `resolve()` and `reject()` as in the spec.
  try {
    // If the executor function throws a sync exception, we consider that
    // a rejection. Keep in mind that, since `resolve()` or `reject()` can
    // only be called once, a function that synchronously calls `resolve()`
    // and then throws will lead to a fulfilled promise and a swallowed error
    executor(resolve, reject);
  } catch (err) {
    reject(err);
  }
}

then()功能更加容易。请记住,该then()函数有两个参数,onFulfilled()onRejected()。该then()函数负责确保onFulfilled()是否实现onRejected()了promise以及是否rejected了promise。如果promise已经解决或被rejected,then()应立即调用onFulfilled()onRejected()*。如果Promise仍未完成,then()则应将函数推入$chained数组,以便resolve()andreject()函数可以调用它们。

then(onFulfilled, onRejected) {
  if (this.$state === 'FULFILLED') {
    onFulfilled(this.$internalValue);
  } else if (this.$state === 'REJECTED') {
    onRejected(this.$internalValue);
  } else {
    this.$chained.push({ onFulfilled, onRejected });
  }
}

ES6规范说,调用.then()已解决或被rejected的Promise意味着onFulfilled()onRejected()应在下一个事件周期上调用。由于本文的代码仅是一个教学示例,而不是该规范的确切实现,因此该实现将忽略此详细信息。

promise链(GitHub Gist

上面的示例专门忽略了Promise最复杂,最有用的部分:chaining。链的想法是,如果onFulfilled()oronRejected()函数返回一个promise,then()则应返回一个“锁定”以匹配返回的promise状态的新promise。例如:

p = new MyPromise(resolve => {
  setTimeout(() => resolve('World'), 100);
});

p.
  then(res => new MyPromise(resolve => resolve(`Hello, ${res}`))).
  // Will print out 'Hello, World' after approximately 100ms
  then(res => console.log(res));

下面是.then()返回promise的新函数,因此您可以进行链接。

then(onFulfilled, onRejected) {
  return new MyPromise((resolve, reject) => {
    // Ensure that errors in `onFulfilled()` and `onRejected()` reject the
    // returned promise, otherwise they'll crash the process. Also, ensure
    // that the promise
    const _onFulfilled = res => {
      try {
        // If `onFulfilled()` returns a promise, trust `resolve()` to handle
        // it correctly.
        resolve(onFulfilled(res));
      } catch (err) {
        reject(err);
      }
    };
    const _onRejected = err => {
      try {
        reject(onRejected(err));
      } catch (_err) {
        reject(_err);
      }
    };
    if (this.$state === 'FULFILLED') {
      _onFulfilled(this.$internalValue);
    } else if (this.$state === 'REJECTED') {
      _onRejected(this.$internalValue);
    } else {
      this.$chained.push({ onFulfilled: _onFulfilled, onRejected: _onRejected });
    }
  });
}

现在then()返回一个promise。但是,仍有一些工作要做:如果onFulfilled()返回promise,则resolve()需要能够处理它。为了支持此resolve()功能,该功能将需要then()在两步递归舞蹈中使用。以下是扩展resolve()功能。

const resolve = res => {
  // A promise is considered "settled" when it is no longer
  // pending, that is, when either `resolve()` or `reject()`
  // was called once. Calling `resolve()` or `reject()` twice
  // or calling `reject()` after `resolve()` was already called
  // are no-ops.
  if (this.$state !== 'PENDING') {
    return;
  }

  // If `res` is a "thenable", lock in this promise to match the
  // resolved or rejected state of the thenable.
  const then = res != null ? res.then : null;
  if (typeof then === 'function') {
    // In this case, the promise is "resolved", but still in the 'PENDING'
    // state. This is what the ES6 spec means when it says "A resolved promise
    // may be pending, fulfilled or rejected" in
    // http://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects
    return then(resolve, reject);
  }

  this.$state = 'FULFILLED';
  this.$internalValue = res;
  // If somebody called `.then()` while this promise was pending, need
  // to call their `onFulfilled()` function
  for (const { onFulfilled } of this.$chained) {
    onFulfilled(res);
  }

  return res;
};

为了简单起见,上面的示例省略了关键细节,即一旦“promise”被“锁定”以匹配另一个promise,调用resolve()reject()为“无操作”,该细节便会消失。在上面的示例中,您可以resolve()向未决的Promise抛出错误,然后在res.then(resolve, reject)上面没有操作。这仅是示例,而不是ES6 Promise规范的完整实现。

上面的代码说明了“已解决”promise和“已实现”promise之间的区别。这种区别是微妙的,与promise链有关。“已解决”不是实际的promise状态,而是ES6规范中定义术语。当您致电resolve()promise被视为已解决时,可能会发生以下两种情况之一:

  • 如果您resolve(v)在哪里v不是promise,那么promise将立即变为现实。在这种简单情况下,“解决”和“实现”是同一回事。
  • 如果您resolve(v)v另一个promise中调用,那么promise会一直pending,直到v解决或rejected为止。在这种情况下,promise已“解决”,但仍未完成。

使用async/await

请记住,await关键字会暂停async函数的执行,直到等待的promise已解决为止。现在您有了一个基本的自制Promise库,让我们看看将其与async/await一起使用时会发生什么。console.log()向该then()函数添加一条语句:

then(onFulfilled, onRejected) {
  console.log('Then', onFulfilled, onRejected, new Error().stack);
  return new MyPromise((resolve, reject) => {
    /* ... */
  });
}

现在,让我们来看await一个实例,MyPromise看看会发生什么。

run().catch(error => console.error(error.stack));

async function run() {
  const start = Date.now();
  await new MyPromise(resolve => setTimeout(() => resolve(), 100));
  console.log('Elapsed time', Date.now() - start);
}

注意.catch()上面的call。该catch()功能是ES6 Promise规范的核心部分。本文将不涉及太多细节,因为.catch(f)它等效于.then(null, f),因此没有太多内容。

以下是输出。注意,await.then()使用onFulfilled()和隐式调用,并onRejected()深入到V8的C ++内部。另外,await始终等到下一个滴答声再致电.then()

Then function () { [native code] } function () { [native code] } Error
    at MyPromise.then (/home/val/test/promise.js:63:50)
    at process._tickCallback (internal/process/next_tick.js:188:7)
    at Function.Module.runMain (module.js:686:11)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3
Elapsed time 102

总结

async/await功能强大,但除非您了解promise的基础知识,否则很难掌握。promise有很多细微差别,例如执行程序函数中的同步错误被捕获,以及promise一旦解决就无法更改状态的事实,这使得async/await成为可能。一旦对promise有了深刻的了解,async/await就变得容易多了。

参考

Write Your Own Node.js Promise Library from Scratch

相关文章

网友评论

      本文标题:从头开始编写自己的Node.js Promise库

      本文链接:https://www.haomeiwen.com/subject/wvffhltx.html