• js异步编程


    前言

    以一个煮饭的例子开始,例如有三件事,A是买菜、B是买肉、C是洗米,最终的结果是为了煮一餐饭。为了最后一餐饭,可以三件事一起做,也可以轮流做,也可能C需要最后做(等A、B做完),这三件事是相关的,抽象起来有三种场景。

    顺序做

    先买菜,再买肉,最后洗米, 即 A->B->C。

    并发做

    买菜,买肉,洗米,一起做。

    交集做

    买菜,买肉必须先做完,才能做洗米。

    场景就是这样,接下来就是如何考虑用js实现。

    function A(callback){
        setTimeout(function(){
              callback("买好菜了");
         }, 1000);
    }
    function B(callback){
         setTimeout(function(){
              callback("买好肉了");
         }, 2000);
    }
    function C(callback){
         setTimeout(function(){
              callback("洗米好了");
         }, 3000);
    } 

    普通做法(无任何模式)

    顺序做

    A(function(res){
         console.log(res);
         B(function(res){
              console.log(res);
              C(function(res){
                   console.log(res);
                   console.log("煮饭啦~~~");
              });
         });
    });

    这就是传说中的回调地狱,实际的场景嵌套的层级可能还更多。

    并发做

    var cook_count = 0;
    A(function(res){
         callback(res);
    });
    B(function(res){
         callback(res);
    });
    C(function(res){
         callback(res);
    });
    function callback(res){
         console.log(res);
         cook_count++;
         if(cook_count == 3){
               console.log("煮饭啦~~~");
         }
    }

    设置一个计数器,记录事情发展,到计数到指定的值,即为结束。

    交集做

    交集做也可以使用计数器做。

    var cook_count = 0;
    A(function(res){
         callback(res);
    });
    B(function(res){
         callback(res);
    });
    function callback(res){
         console.log(res);
         cook_count++;
         if(cook_count == 2){
         C(function(res){
             console.log(res);
             console.log("煮饭啦~~~");
         });
         }
    }

    发布/订阅模式

    我在上一篇文章《观察者模式与发布/订阅模式区别》有说明这种模式的特点,以及附上了js实现方式(这种模式有很多种实现的方式,这个只是简单的一种)。我们可以用这种模式来解决js异步编程。

    顺序做

    function A(){
      setTimeout(function(){
        pubsub.publish("A_FINISH", "买好菜了");
      }, 1000);
    }
    function B(){
      setTimeout(function(){
        pubsub.publish("B_FINISH", "买好肉了");
      }, 2000);
    }
    function C(){
      setTimeout(function(){
        pubsub.publish("C_FINISH", "洗米好了");
      }, 3000);
    }
    A();
    pubsub.subscribe("A_FINISH",  B);
    pubsub.subscribe("B_FINISH",  C);
    pubsub.subscribe("C_FINISH", function(){
      console.log("煮饭啦~~~");
    });

    PS:还有一直很常用的jquery的自定义事件也是类似的写法(on、trigger)。

    并发做

    订阅/发布并没有真正优化到异步编程,所以并发做其实还要用其他方式去检查事情完成状态,就如之前的计数。

    var cook_count = 0;
    pubsub.subscribe("A_FINISH",  callback);
    pubsub.subscribe("B_FINISH",  callback);
    pubsub.subscribe("C_FINISH",  callback);
    function callback(res){
         console.log(res);
         cook_count++;
         if(cook_count == 3){
               console.log("煮饭啦~~~");
         }
    }

    PS:交集做也是类似,就不举例了。

    Promise/Deferred模式

    ES6规范包含了Promise,Promise对象可以理解为一次将要执行的操作(常常被用于异步操作),使用了 Promise 对象之后可以用一种链式调用的方式来组织代码,让代码更加直观。。

    顺序做

    A().then(function(res) {
        console.log(res);
        return B();
    }).then(function(res) {
        console.log(res);
        return C();
    }).then(function(res) {
        console.log(res);
        console.log("煮饭啦~~~");
    });
    function A() {
        return new Promise(function (resolve, reject) {
             setTimeout(function(){
                resolve("买好菜了");
            }, 1000);
        });
    }
    function B() {
        return new Promise(function (resolve, reject) {
              setTimeout(function(){
                   resolve("买好肉了");
              }, 2000);
        });
    }
    function C() {
        return new Promise(function (resolve, reject) {
              setTimeout(function(){
                   resolve("洗米好了");
              }, 2000);
        });
    }

    then方法定义:then(fulfilledAHandler, errorHandler),对应着Promise的resolve, reject。

    并发做

    Promise.all方法可做并发调用,当所有Promise 对象都变为完成态或失败态时,回调将被执行。

    Promise.all([A(), B(), C()]).then(function (result) {
        console.log(result); //["买好菜了", "买好肉了", "洗米好了"]
    }); 

    第三方框架实现

    jQuery之Deferred

    Deferred对象(jQuery的回调函数解决方案),对异步编程做了优化,有人总结四种功能。

    一、 提供ajax操作的链式写法

    $.ajax("test.html")
        .done(function(){ alert("哈哈,成功了!"); })
        .fail(function(){ alert("出错啦!"); });

    PS:高于1.5.0版本,返回的是deferred对象,可以进行链式操作。

    二、同一操作的多个回调函数

    $.ajax("test.html")
         .done(function(){ alert("哈哈,成功了!");} )
         .done(function(){ alert("第二个回调函数!");} );

    三、为多个操作指定回调函数

    $.when($.ajax("test1.html"), $.ajax("test2.html"))
         .done(function(){ alert("哈哈,成功了!"); })
         .fail(function(){ alert("出错啦!"); });

    PS:如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。

    四、普通操作的回调函数接口

    $(function(){
       var A = function(){ //并不是ajax
         var dtd = $.Deferred();
           setTimeout(function(){
              dtd.resolve("买好菜了");
           }, 1000);
          return dtd.promise(); // 返回promise对象
       };
     
        var B = function(){
          var dtd = $.Deferred();
            setTimeout(function(){
                dtd.resolve("买好肉了");
            }, 2000);
          return dtd.promise(); // 返回promise对象
       };
     
        var C = function(){
          var dtd = $.Deferred();
            setTimeout(function(){
                dtd.resolve("洗米好了");
            }, 2000);
          return dtd.promise(); // 返回promise对象
       };
       $.when(A(), B(), C())
        .done(function(v1, v2, v3){  console.log("煮饭啦~~~", v1 , v2, v3); })
        .fail(function(){ alert("出错啦!"); });
    });

    PS:deferred.promise()方法。它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。

    总结

    发布/订阅模式相对算是一种较为原始的方式,Promise/Deferred模式贡献了一个非常不错的异步任务模型的抽象,而实际上我们可能会常用的是第三方库来做异步编程,毕竟这些比较成熟及方便。另外如果有其他更好的异步编程方式请回复我一下,让我可以进行总结。

    参考文献

    jQuery的deferred对象详解》 by 阮一峰

    本文为原创文章,转载请保留原出处,方便溯源,如有错误地方,谢谢指正。
  • 相关阅读:
    面向对象并不是必要的
    linq 总结
    垃圾自动回收的一个方案
    随手记 手机软件的不足,和开发自己理财软件的想法
    以人的角度去解决问题
    浮点数比较
    集中原则——软件设计之道
    云在何方
    我遇到了DLL地狱
    在C#.net中如何操作XML
  • 原文地址:https://www.cnblogs.com/lovesong/p/5323973.html
Copyright © 2020-2023  润新知