把书看薄-深入浅出nodejs-异步控制

#第4章 异步编程
异步编程对于前端之重要性不言而喻,而 深入浅出node.js书中这一章也写得非常精彩,所以必须得单拿出来做笔记。

异步编程的难点

  • 异常处理,try catch无法获取到异步的代码
  • 函数嵌套过深
  • 阻塞代码
  • 多线程编程

    异步编程解决方案

  • 事件发布/订阅模式

    • node的事件来源于 events模块,类似于dom里面的事件,具有 on/addListener、once、removeListener、emit等等方法,如:
      //订阅
      emitter.on("event1", function (message) {
        console.log(message);
      });
      //发布
      emitter.emit("event1", "I am message!");
      
    • 有两点值得注意的地方:

      1 如果一个事件超过10个侦听器,将会得到一条警告。调用 emitter.setMaxListeners(0);可以将这个限制去掉。
      2 如果在运行期间的错误触发了error事件,EventEmitter会检查是否对error事件添加过监听,如果监听了,交给监听器处理,否则错误作为异常抛出。

    • 解决雪崩问题
      雪崩问题是指高并发缓存失效的场景。
      var select = function (callback) {
        db.select("SQL", function (results) {
            callback(results);
        });
      }
      //如果站点刚刚启动,没有缓存,上面又没有锁,并发情况下数据库会反复查询
      //改进一 加个锁
      var status = "ready";
      var select = function (callback) {
        if (status === "ready") {
            status = "pending";
            db.select("SQL", function (results) {
                status = "ready";
                callback(results);
            });
        }
      }
      //上述问题在于,并发过来的请求只有一个会返回数据,其他的无法得到。
      //继续改进
      var proxy = new events.EventEmitter();
      var status = "ready";
      var select = function (callback) {
        proxy.once("selected", callback);
        if (status === "ready") {
            status = "pending";
            db.select("SQL", function (results) {
                proxy.emit("selected", results);
                status = "ready";
            });
        }
      }
      
    • 通过哨兵变量来处理监听多异步处理情况。说白了其实就是每个异步操作完成了调用一下固定函数,count++,只有count = 固定值的时候,才处理最后的回调。
  • Promise

    • promiseA+规范已经被集成到ES6里面,可以看一篇我小伙伴的译文 promiseA+规范
    • 深入浅出里面着重介绍了 promise/A,也就是promise/deffered模式。也实现了一版简单的:

      var Promise = function () {
      EventEmitter.call(this);
      }
      Promise.prototype.then = function (fullfilledHandler, errorHandler, progressHandler) {
      if (typeof fullfilledHandler === 'function') {
        this.once('success', fullfilledHandler);    
      }
      if (typeof errorHandler === "function") {
        this.on("error", errorHandler);
      }
      if (typeof progressHandler === "function") {
        this.on("progress", progressHandler);
      }
      return this;
      }
      var Deffered = function () {
      this.state = "unfulfilled";
      this.promise = new Promise();
      }
      Deffered.prorype.resolve = function (obj) {
      this.state = "fulfilled";
      this.promise.emit("success", obj);
      }
      Deffered.prorype.reject = function (obj) {
      this.state = "failed";
      this.promise.emit("error", err);
      }
      
      其实弄懂了原理promise也是非常简单,promise对象维护了3个状态,然后通过then方法注册成功失败事件,在deffer对象里面去触发,最本质的原理其实还是通过事件机制实现。 有一个问题是上面明显一个对象就能搞定,为什么还要引入deffer对象? 先看使用上面:
      var promisefy = function () {
      var deffered = new Deffered();
      var result = "";    
      res.on("data", function (chunk) {
        results += chunk;
        deffered.progress(chunk);
      });
      
      res.on("end", function () {
        deffered.resolve(result);
      });
      
      res.on("error", function (err) {
        deffered.reject(err);
      });
      return deffered.promise;
      }
      //调用
      promisfy(res).then(function (){
      //done
      }, function () {
      //err
      });
      
      从上述代码可以看出来: 1 promisefy用作在异步函数里面注入固定的代码 resolve/reject ,返回promise对象。 2 promise对象注册事件,从而实现1步骤里面的异步完成时来触发这些事件。 3 为什么要把deffer和promise分开,是因为 除了封装在promisefy里面的deffer对象,promise对象是拿不到resolve和reject事件的,从而保证状态不被外界修改,promise只能用作注册函数,而触发必须在回调里面用deffer对象触发,**这样设计是出于安全性考虑,promisefy调用之后返回的promise对象保证只能绑定不能修改内部的状态**。 4 想想这种模式比事件订阅模式高级的地方就是 不用自己去维护那个 done 方法,虽然还需要自己写promisefy方法,但是这个方法完全可以抽象出来。用指定的API来保证方法调用,比如ES6里面的 Promise方法,就是一个通用的 promisefy方法。 5 实现多异步操作:
      Deffered.prototype.all = function (promise) {
       var count = promise.length;
       var that = this;
       promise.forEach(function (promise, i) {
         promise.then(function (data) {
           count--;
           result[i] = data;
           that.resolve(results);
         });
       })
      }
      
      其实就是维护了一个promise队列,当全部都完成时再调用最后的回调。 6 链式调用:
      //实现链式调用就是在promise对象上面维护一个按顺序的queue,调用then的时候按顺序压入队列,每次resolve改成拿队头的回调函数出来执行。之前的promise.then是绑定的once方法,现在其实就不需要事件机制了,维护事件队列就行了。 
      
喝杯咖啡,交个朋友