会用不如会造 - Promise

作者: kdepp | 来源:发表于2016-08-04 13:38 被阅读342次
    Promise ?

    为什么要自己造 Promise

    • Promise已经是Ecmascript 6标准中的内容
    • Promise是async & await的基础
    • 不造一下,怎么才能把Promise/A+的标准了解的更透彻?

    本文结构

    • Promise认知 - 40%
    • Promise轮子 - 60%

    对Promise有足够了解的读者,可酌情阅读跳过认知部分。

    Promise的认知阶梯

    最早开始使用Promise是为了不写回调形式的函数;后来发现Promise可以让nodejs里function (err, data)的写法变得更友好;后来开始用 q;再后来发现 bluebird 这样顺应 Promise/A+ 的API用起来更顺。然后发现bluebird这个词原本就是一个combinator,compose其实是它的本意;然后发现Promise也是一种Monad。

    以上是笔者自己对Promise的认知之路,理解上不断产生新的变化,其中真正可以提炼出来的步骤如下:

    Phase 1 - Callback Hell 救星

    首先,callback是异步操作的产物,同步操作不需要回调函数。早先在纯浏览器环境中,会用到callback的有:

    • ajax请求
    • DOM事件绑定
    • setTimeout

    从nodejs开始,异步IO是根本特性,因而callback变得无处不在,然后出现了nodejs的callback参数标准:function (err, data)

    /*
     * callback version
     */
    var sillyCopy = function (src, target, callback) {
    fs.readFile(src, function (err, data) {
      if (err)  return callback(err);
      fs.write(target, data, function (err) {
        if (err) return callback(err);
        fs.write(src, 'this is silly', function (err) {
          callback(err);
        });
      });
    });
    
    sillyCopy('my/dir/src.txt', 'my/dir/target.txt', function (err) {
      if (err)  console.log(err);
      else     console.log('done');
    });
    
    /*
     * promise version
     */
    var sillyCopy = function (src, target) {
      return promiseFS.readFile(src)
      .then(function (data) {
        return promiseFS.writeFile(target));
      })
      .then(function() {
        return promiseFS.writeFile(src, 'this is silly');
      });
    };
    
    sillyCopy('my/dir/src.txt', 'my/dir/target.txt')
    .then(function (data) {
      console.log(data);
    }, function (err) {
      console.log(err);
    });
    

    注: 以上 promise 版本的 sillyCopy 中用到了promise化的 fs。

    可以看到,使用了promise之后的异步操作定义,变得更加更加清晰

    Phase 2 - 串联异步操作

    var flow = function (list) {
      return function (arg) {
        return list.reduce(function (p, fn) {
          return p.then(fn);
        }, Promise.resolve(arg));
    };
    
    var sillyProcess = flow([
      pseudo_ReadFile,
      pseudo_AjaxPostSearchFileText,
      pseudo_ReadDB,
      pseudo_WriteToLocalFile
    ])
    

    这里的flow就是同步模式下用到的 compose,用于将各种异步操作进行串联,在充满异步操作充满Promise的环境中,一个简单的flow实现可以让代码变得清晰易懂

    Phase 3 - 异常捕获

    回看之前的样例代码,callback形式会让对异常的捕获散布在各层级的callback里,看着闹心有没有?

    var sillyCopy = function (src, target, callback) {
    fs.readFile(src, function (err, data) {
      if (err)  return callback(err);
      fs.write(target, data, function (err) {
        if (err) return callback(err);
        fs.write(src, 'this is silly', function (err) {
          callback(err);
        });
      });
    });
    
    // This is what we want
    sillyCopy(src, target)
    .catch(function (err) {
      console.log(err);
    });
    

    在实际Coding过程中,异常处理是非常重要的步骤,除了上面看到的串行的异步代码,还有并行的异步操作,各种复杂情况导致对异常的统一处理变得十分关键,仅异常处理的代码会变得更容易维护。

    Phase 4 - Thenable 接口

    Thenable在一般场景中我们碰到的不算特别多,但它的价值却不能被低估。Thenable的价值在于,它是一个统一的接口定义,它让我们可以把不同Library里的、使用不同Promise实现的异步操作,放在一起使用。

    例如,jQuery有它自己内部对Promise的实现 (早起使用 defer 创建 Promise 对象 ,后来慢慢统一到了 Promise/A+ 的标准模式)。还有 npm 上成千上万的包,都可能使用了不同的 Promise 实现。


    开始造 Promise

    既然是造轮子,先看下按照什么标准来造,然后确定一下造的步骤,剩下的就是开始coding了。

    标准

    步骤

    我们把实现过程分成3部分,类似于对Promise的认知过程

    1. 串联异步操作,忽略异常处理
    • 构造函数
    • 保留 resolve 结果
    • then,要满足 resolve 之后的 then 也能得到执行
    1. 异常处理
    • then增加reject参数
    • 捕获 resolve 和 reject 过程中的异常
    1. Thenable
    • 允许返回一个 Thenable 的对象
    • 对 Thenable.then(resolve, reject) 的整体过程进行异常捕获

    串联异步操作

    • 一个Promise对象可以多次调用 then
    • 保存 resolve 结果
    var MyPromise = function (executor) {
      var self = this;
    
      this.next = [];
      this._value = null;
      this._state = 0;
    
      var resolve = function (value) {
        self._state = 1;
        self._value = value;
        self.next.forEach(function (fn) {
          fn(value);
        });
      };
      executor(resolve);
    };
    
    MyPromise.prototype.then = function (onResolve) {
      var self = this;
    
      if (!self._state) {
        return MyPromise.resolve(onResolve(self._value));
      }
    
      return new Promise(function (resolve) {
        self.next.push(function (value) {
          resolve(onResolve(value));
        });
      });
    };
    
    MyPromise.resolve = function (value) {
      return new Promise(function (resolve) {
        resolve(value);
      });
    };
    
    /*
     * Demo
     */
    var p = Promise.resolve(2);
    
    p
    .then(function (a) { return a * 2 })
    .then(function (a) { console.log(a) });
    // output: 4
    
    setTimeout(function () {
      p
      .then(function (a) { return a * 3 })
      .then(function (a) { console.log(a) });
      // output: 6
    }, 100);
    
    

    至此,串联异步操作部分就算完成了,一个不带 reject 和 异常捕获的 Thenable 实例。从上面的 Demo 可以看出,我们的 Promise API 已初具雏形。但不要得意的太早,Promise 的 API 本身并不复杂:

    • Promise API
      • Promise Constructor
      • Promise.prototype.then
      • Promise.prototype.catch
      • Promise.all
      • Promise.resolve
      • Promise.reject

    不过同等功能下,越是简单的 API 设计,其内部逻辑就会越复杂。jQuery.$ 就是一个很好的例子。

    异常捕获

    • then 接受两个参数, onResolve & onReject
    • 对 onResolve 和 onReject 执行过程中产生的异常进行捕获
    var PENDING = 0;
    var RESOLVED = 1;
    var REJECTED = 2;
    
    // Note: onResolve 和 onReject 都会在这里包一层
    // 为了让两者的返回值都能继续向下传递到下一个 onResolve
    var wrapHandler = function (state, handler, p2) {
      return function (val) {
        var next = state == RESOLVED ? p2._resolve : p2._reject;
        var ret, then, type, p3;
    
        if (!handler || typeof handler !== 'function') {
          return next(val);
        }
    
        try {
          ret = handler(val);
        } catch (e) {
          return p2._reject(e);
        }
    
        p2._resolve(ret);
      };
    };
    
    var genHandler = function (state, p) {
      return function (value) {
        if (p._state !== PENDING) return;
        p._state = state;
        p._value = value;
        p.next.forEach(function (obj) {
          setTimeout(function () {
            obj[state === RESOLVED ? 'onResolve' : 'onReject'](value);
          }, 0);
        });
      };
    };
    
    var MyPromise = function (executor) {
      var self = this;
    
      this.next = [];
      this._value = null;
      this._state = PENDING;
      this._resolve = genHandler(RESOLVED, self);
      this._reject  = genHandler(REJECTED, self);
    
      executor(this._resolve, this._reject);
    };
    
    MyPromise.prototype.then = function (onResolve, onReject) {
      var self = this;
      var p2   = new MyPromise(function () {});
      var ret;
    
      if (self._state === PENDING) {
        self.next.push({
          onResolve: wrapHandler(RESOLVED, onResolve, p2),
          onReject:  wrapHandler(REJECTED, onReject,  p2)
        });
      } else {
        setTimeout(function () {
          wrapHandler(self._state, self._state === RESOLVED ? onResolve : onReject, p2)(self._value);
        }, 0);
      }
    
      return p2;
    };
    
    MyPromise.resolve = function (value) {
      return new Promise(function (resolve, reject) {
        resolve(value);
      });
    };
    
    MyPromise.reject = function (value) {
      return new Promise(function (resolve, reject) {
        reject(value);
      });
    };
    
    /*
     * Demo
     */
    MyPromise.reject(1)
    .then(null, function (e) {
      console.log(e);
      return e + 2;
    })
    .then(function (n) {
      console.log(n);
      throw n * 2;
    })
    .then(null, function (e) {
      console.log(e);
    });
    // output: 1
    // output: 3
    // output: 6
    

    这里有几个非常重要的 Promise 使用方法:

    • 没有设置 onResolve 或 onReject,对应的返回值或异常将原封不动向下传递
      Promise.resolve(1)
      .then()
      .then(function (n) { console.log(n); });
      // output: 1
      
      Promise.reject(2)
      .then()
      .then(null, function (e) { console.log(e); });
      // output: 2
      
    • onReject 的返回值会继续向下传递给下一个 onResolve
      Promise.reject(1)
      .then(null, function (e) { return e + 2; })
      .then(function (n) { console.log(n); };
      // output: 3
      

    Thenable 接口

    • Thenable 类型的处理
    • 出现返回Promise实例自身的情况,要抛出 TypeError
    var PENDING = 0;
    var RESOLVED = 1;
    var REJECTED = 2;
    
    var wrapHandler = function (state, handler, p2) {
      return function (val) {
        var next = state == RESOLVED ? p2._resolve : p2._reject;
        var ret, then, type, p3;
    
        if (!handler || typeof handler !== 'function') {
          return next(val);
        }
    
        try {
          ret = handler(val);
        } catch (e) {
          return p2._reject(e);
        }
    
        solve(ret, p2);
      };
    };
    
    // 处理所有可能情况的 val,包括 Thenable
    var solve = function (val, p2) {
      if (val === p2) {
        return p2._reject(new TypeError('no promise circle allowed'));
      }
    
      type = typeof val;
    
      if (type === 'function' || type === 'object' && val !== null) {
        try {
          then = val && val.then;
        } catch (e) {
          return p2._reject(e);
        }
    
        if (typeof then === 'function') {
          try {
            return then.call(val, function (val2) {
              solve(val2, p2);
            }, p2._reject);
          } catch (e) {
            return p2._reject(e);
          }
        }
      }
    
      return p2._resolve(val);
    }
    
    var genHandler = function (state, p) {
      return function (value) {
        if (p._state !== PENDING) return;
        p._state = state;
        p._value = value;
        p.next.forEach(function (obj) {
          setTimeout(function () {
            obj[state === RESOLVED ? 'onResolve' : 'onReject'](value);
          }, 0);
        });
      };
    };
    
    var MyPromise = function (executor) {
      var self = this;
    
      this.next = [];
      this._value = null;
      this._state = PENDING;
      this._resolve = genHandler(RESOLVED, self);
      this._reject  = genHandler(REJECTED, self);
    
      executor(this._resolve, this._reject);
    };
    
    MyPromise.prototype.then = function (onResolve, onReject) {
      var self = this;
      var p2   = new MyPromise(function () {});
      var ret;
    
      if (self._state === PENDING) {
        self.next.push({
          onResolve: wrapHandler(RESOLVED, onResolve, p2),
          onReject:  wrapHandler(REJECTED, onReject,  p2)
        });
      } else {
        setTimeout(function () {
          wrapHandler(self._state, self._state === RESOLVED ? onResolve : onReject, p2)(self._value);
        }, 0);
      }
    
      return p2;
    };
    
    MyPromise.resolve = function (value) {
      return new Promise(function (resolve, reject) {
        resolve(value);
      });
    };
    
    MyPromise.reject = function (value) {
      return new Promise(function (resolve, reject) {
        reject(value);
      });
    };
    
    

    到此为止,一个满足 Promise/A+ 规范的 MyPromise 就算造完了。

    完整代码在我的 github 上可以找到

    https://github.com/kdepp/mash/blob/master/mash.js

    相关文章

      网友评论

        本文标题:会用不如会造 - Promise

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