• 短小强悍的JavaScript异步调用库


    对于博文 20行完成一个JavaScript模板引擎 的备受好评我感到很惊讶,并决定用此文章介绍使用我经常使用的另一个小巧实用的工具.我们知道,在浏览器中的 JavaScript 绝大部分的操作都是异步的(asynchronous),所以我们一直都需要使用回调方法,而有时不免陷入回调的泥淖而欲死欲仙。

      假设我们有两个 functions ,我们顺序地在一个后面执行完后调用另一个。他们都操作同一个变量。第一个设置它的值,第二个使用它的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var value; 
    var A = function() { 
        setTimeout(function() { 
            value = 10; 
        }, 200); 
    var B = function() { 
        console.log(value); 
    }

      那么,现在如果我们运行 A();B(); 我们将在控制台看到输出为 undefined . 之所以会这样是因为 A 函数使用了异步方式设置 value 。我们能做的就是传一个回调函数给A,并让函数A在执行完后执行回调函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var value; 
    var A = function(callback) { 
      setTimeout(function() { 
        value = 10; 
        callback(); 
      }, 200); 
    }; 
    var B = function() { 
      console.log(value); 
    }; 
     
    A(function() { 
      B(); 
    });

      这样确实有用,但想象一下加入我们需要运行5个或更多方法时将会发生什么。一直传递回调函数将会导致混乱和非常不雅观的代码。
    好的解决办法是写一个工具函数,接受我们的程序并控制整个过程。让我们先从最简单的开始:

    1
    2
    3
    var queue = function(funcs) { 
        // 接下来请看,董卿??? 
    }

      接着,我们要做的是通过传递A和B来运行该函数 - queue([A, B])。我们需要取得第一个函数并执行它。

    1
    2
    3
    4
    var queue = function(funcs) { 
        var f = funcs.shift(); 
        f(); 
    }

      如果执行这段代码,您将看到一个 TypeError: undefined is not a function。这是因为 A函数没收到回调参数但却试图运行它。让我们换一种调用方法。

    1
    2
    3
    4
    5
    6
    7
    var queue = function(funcs) { 
        var next = function() { 
            // ... 
        }; 
        var f = funcs.shift(); 
        f(next); 
    };

      在 A执行完后会调用 next 方法。将下一步操作放在 next 函数列表中是个很好的做法。我们可以将代码归拢在一起,而且我们能够传递整个数组(即便数组中有很多函数等待执行)。

    1
    2
    3
    4
    5
    6
    7
    var queue = function(funcs) { 
        var next = function() { 
            var f = funcs.shift(); 
            f(next); 
        }; 
        next(); 
    };

      到了这一步,我们基本上达到了我们的目标。即函数A 执行后,接着会调用 B,打印出变量的正确值。这里的关键是 shift 方法的使用。它删除数组的第一个元素并返回该元素。一步一步执行下去, funcs数组就会变成 empty(空的)。所以,这可能会导致另一个错误。为了证明这一观点,让我们假设我们仍然需要运行这两个功能,但我们不知道他们的顺序。在这种情况下,两个函数都应该接受回调参数(callback )并执行它。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var A = function(callback) { 
        setTimeout(function() { 
            value = 10; 
            callback(); 
        }, 200); 
    }; 
    var B = function(callback) { 
        console.log(value); 
        callback(); 
    };

      当然,我们会得到 TypeError: undefined is not a function.
    要阻止这一点,我们应该检查funcs数组是否为空。

    1
    2
    3
    4
    5
    6
    7
    8
    var queue = function(funcs) { 
        (function next() { 
            if(funcs.length > 0) { 
                var f = funcs.shift(); 
                f(next); 
            
        })(); 
    };

      我们所做的就是定义 next 函数并调用它。这种写法减少了一点代码。

      让我们试着想象尽可能多的情况。比如当前执行功能的 scope 。函数内的 this 关键字可能指向了全球的 window 对象。,如果我们可以设置自己的scope 当然是件很酷的事情。

    1
    2
    3
    4
    5
    6
    7
    8
    var queue = function(funcs, scope) { 
        (function next() { 
              if(funcs.length > 0) { 
                  var f = funcs.shift(); 
                  f.apply(scope, [next]); 
              
        })(); 
    };

      我们为这个tiny 类库增加了一个参数。接着我们使用 apply  函数,而不是直接调用 f(next),来设置scope 并将参数 next 传递进去。同样的功能,但漂亮多了。

      我们需要的最后一个特性,就是是函数间传递参数的能力。当然我们不知道具体会有多少参数将被使用。这就是为什么我们需要使用 arguments 变量的原因。你可能知道,该变量在每个 JavaScript函数中都是可用的,代表了传进来的参数。它就和一个数组差不多,但不完全是。因为在 apply 方法中我们需要使用真正的数组,使用一个小窍门来进行转换。

    1
    2
    3
    4
    5
    6
    7
    8
    var queue = function(funcs, scope) { 
        (function next() { 
              if(funcs.length > 0) { 
                  var f = funcs.shift(); 
                  f.apply(scope, [next].concat(Array.prototype.slice.call(arguments, 0))); 
              
        })(); 
    };

      下面是测试的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 测试代码 
    var obj = { 
        value: null 
    }; 
     
    queue([ 
        function(callback) { 
            var self = this
            setTimeout(function() { 
                self.value = 10; 
                callback(20); 
            }, 200); 
        }, 
        function(callback, add) { 
            console.log(this.value + add); 
            callback(); 
        }, 
        function() { 
            console.log(obj.value); 
        
    ], obj);

      执行后的输出为:

    1
    2
    30 
    10

      为了代码的可读性和美观,我们将部分相关的代码移到一行内:

    1
    2
    3
    4
    5
    6
    7
    var queue = function(funcs, scope) { 
        (function next() { 
              if(funcs.length > 0) { 
                  funcs.shift().apply(scope || {}, [next].concat(Array.prototype.slice.call(arguments, 0))); 
              
        })(); 
    };

      你可以 点击这里查看并调试相关代码 ,完整的测试代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    var queue = function(funcs, scope) { 
        (function next() { 
              if(funcs.length > 0) { 
                  funcs.shift().apply(scope || {}, [next].concat(Array.prototype.slice.call(arguments, 0))); 
              
        })(); 
    }; 
     
    var obj = { 
        value: null 
    }; 
     
    queue([ 
        function(callback) { 
            var self = this
            setTimeout(function() { 
                self.value = 10; 
                callback(20); 
            }, 200); 
        }, 
        function(callback, add) { 
            console.log(this.value + add); 
            callback(); 
        }, 
        function() { 
            console.log(obj.value); 
        
    ], obj);
  • 相关阅读:
    javascript高级编程笔记03(正则表达式)
    javascript高级编程笔记02(基本概念)
    javascript高级编程笔记01(基本概念)
    ExtJS4加载FormPanel数据的几种方式
    Extjs 更新数据集Ext.PagingToolbar的start参数重置的处理
    四川绵阳 晴
    四川绵阳 阴
    四川绵阳 晴
    在IntelliJ IDEA中添加repository模板
    List分组 用于客服对话分组场景
  • 原文地址:https://www.cnblogs.com/gongcheng9990/p/3987961.html
Copyright © 2020-2023  润新知