前端译趣

纯手写实现自己的nodejs promise 库

2018-05-24  本文已影响2人  linc2046

纯手写实现自己的nodejs promise 库

Promise是js推荐的异步原生元素。
回调函数变得越来越少,特别是现在nodejs已经可以用async/await。

async/await也是基于promise的,所以你需要理解primose才能掌握async/await.

本文将会带你编写自己的promise库,以及如何使用async/await.

什么是Promise?

es6标准中,promise是一个构造函数接受运算函数的类。

promise类的实例拥有then方法。
根据标准promise还有其他的熟悉, 单本教程中你可以暂时忽略。

以下是简单promise类的简单形式。

class MyPrimise {
  constructor(executor) {}

  then(onFullfilled, onRejected) {}
}

运算函数接受两个参数, resolve函数和reject函数。
promise是有3种状态的状态机。

记住这些,实现第一版的MyPromise构造函数很简单。

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

  this.$state = 'PENDING';
  this.$chained = [];

  const resolve = res => {
    if(this.$state !== 'PENDING'){
      return;
    }
  }
}

then函数更简单。记住then函数接受两个参数, onFullfilled 和 onRejected。

then函数负责确保promise完成时调用onFullfilled, promise失败时调用onRejected.

如果Promise已经解决或拒绝, then 应该立即调用onFullfilled 或onRejected。

如果promise仍在等待,then应该将参数推入$chained数组供resolve和reject函数调用。

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

由于本文的代码会实现简版而不是标准的完整实现,本实现将会忽略该细节。

promise 链

以上实例特别湖绿了最复杂也是最有用的部分: 链式调用。

链式调用的实现是如果onFullfilled或onRejected函数返回promise, then方法应该返回一个被锁住的新promise以匹配返回promise的状态。例如:

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

p.
  then(res => new Promise(resolve => resolve(`Hello, ${res}`))).
  then(res => console.log(res));

以下是返回新promise的新then函数,用于链式调用。

then(onFullfilled, onRejected) {
  return new MyPromise((resolve, reject) => {
    const _onFullfilled = res => {
      try {
        resolve(onFullfilled(res));
      } catch(ex){
        reject(ex);
      }
    };

    const _onRejected = err => {
      try{
        reject(onRejected(err));
      } catch(ex){
        reject(ex);
      }
    };

    if(this.$state === 'FULLFILLED'){
      _onFullfilled(this.$internalValue);
    } else if( this.$state === 'REJECTED'){
      _onRejected(this.$internalValue);
    } else {
      this.$chained.push({
        onFullfilled: _onFullfilled,
        onRejected: _onRejected
      });
    }
  });
}

现在then返回一个新promise, 但是还需要完善。
如果onFullfilled返回promise, resolve需要能够处理promise.
为了支持这,rsolve函数需要使用then方法进行两步递归调用。

以下是扩展的resolve函数.

const resove = res => {
  if(this.$state !== 'PENDING'){
    return;
  }

  if(res != null && typeof res.then === 'function'){
    return res.then(resolve, reject);
  }

  this.$state = 'FULLFILLED';
  this.$internalValue = res;

  for(const {onFullfilled} of this.$chained) {
    onFullfilled(res);
  }

  return res;
}

为了保持简洁,以上实例实现了关键部分,只要promise被锁住以匹配另外的promise, 调用resolve或reject就是空调用。

以上实例中,你可以对等待promise调用resolve,抛错, 之后res.then(resolve, reject)也会是空函数。 这只是个例子,没有完全实现ES6 promise标准。

以上代码显示已解决的promise和已完成的promise之间的区别。
差异很小,主要和链式调用有关。
已解决其实并不是真正的promise状态,但它是ES6特性中定义的术语。

当你resolve以上实现,下面之一会发生:

Async/Await

记住await关键字暂停异步函数执行,直到被延迟的promise执行完成。

现在你已经实现了家用promise库,我们看下和async/await一起使用会发生什么。
在then函数中加入console.log语句。

then(onFullfilled, onRejected) {
  console.log('Then'), onFullfilled, onRejected, new Error().stack);

  return new MyPromise((resolve, reject) => {

  });
}

现在,让我们等待MyPromise的实例,看看会发生什么。

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

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

记录上面catch函数调用。

catch函数是ES6 promise特性的核心部分。
本文不会详细介绍,因为 catch(f) 等同于 .then(null, f), 没有太多不同。

以下是输出。注意await 隐式调用 then方法, onFullfilled和OnRejected方法会深入到V8引擎的内部中。
另外, 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会变得更加容易。

译者注

上一篇 下一篇

猜你喜欢

热点阅读