美文网首页前端译趣
纯手写实现自己的nodejs promise 库

纯手写实现自己的nodejs promise 库

作者: linc2046 | 来源:发表于2018-05-24 10:04 被阅读2次

    纯手写实现自己的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种状态的状态机。

    • 等待: 初始状态,意味着promise还未建立
    • 完成: 底层操作已经成功,并存在关联值
    • 拒绝: 底层操作失败,有关联错误

    记住这些,实现第一版的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 });
      }
    }
    
    • 另外,es特性中表示调用已经解决或拒绝的promise上的then方法,意味着 onFullfilled 或者 onRejecte函数应该在下次调用。

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

    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以上实现,下面之一会发生:

    • 如果你调用resolve(v), v不是promise, 那么promise会立刻变成已完成状态。这种情况下,已解决和已完成是一样的。

    • 如果你调用resolve(v), v是另一个promise, 这个promise仍然是等待状态,直到v resolve或rejec。 这种情况下,promise已解决,但仍然是等待状态。

    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会变得更加容易。

    译者注

    • 原文链接

    • 因译者水平有限,如有错误,欢迎指正交流

    相关文章

      网友评论

        本文标题:纯手写实现自己的nodejs promise 库

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