对于博文 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异步调用库的更多相关文章

  1. ♫【异步】短小强悍的JavaScript异步调用库

    短小强悍的JavaScript异步调用库 var queue = function(funcs, scope) { (function next() { if(funcs.length > 0) ...

  2. 用Html5/CSS3做Winform,一步一步教你搭建CefSharp开发环境(附JavaScript异步调用C#例子,及全部源代码)上

    本文为鸡毛巾原创,原文地址:http://www.cnblogs.com/jimaojin/p/7077131.html,转载请注明 CefSharp说白了就是Chromium浏览器的嵌入式核心,我们 ...

  3. 探索Javascript异步编程

    异步编程带来的问题在客户端Javascript中并不明显,但随着服务器端Javascript越来越广的被使用,大量的异步IO操作使得该问题变得明显.许多不同的方法都可以解决这个问题,本文讨论了一些方法 ...

  4. 【javascript 进阶】异步调用

    前言 javascript的中的异步是很重要的概念,特别是ajax的提出,给整个web带来了很大的影响,今天就介绍下javascript的异步编程. 同步与异步 何为同步?何为异步呢? 同步:说白了就 ...

  5. Boost库实现线程池学习及线程实现的异步调用

    A.Boost线程池实现 参考自: Boost库实现线程池实例 原理:使用boost的thread_group存储多个线程,使用bind方法将要处理的函数转换成线程可调用的函数进行执行:使用队列存储待 ...

  6. 异步http接口调用库:httpx

    谈到http接口调用,Requests大家并不陌生,例如,robotframework-requests.HttpRunner等HTTP接口测试库/框架都是基于它开发.这里将介绍另一款http接口测试 ...

  7. JavaScript异步编程的主要解决方案—对不起,我和你不在同一个频率上

    众所周知(这也忒夸张了吧?),Javascript通过事件驱动机制,在单线程模型下,以异步的形式来实现非阻塞的IO操作.这种模式使得JavaScript在处理事务时非常高效,但这带来了很多问题,比如异 ...

  8. 顶级的JavaScript框架、库、工具及其使用

    几乎每隔一个星期,就有一个新的 JavaScript 库席卷网络社区!Web 社区日益活跃.多样,并在多个领域快速成长.想要研究每一个重要的 JavaScript 框架和库,是个不可能完成的任务.接下 ...

  9. javascript异步编程的前世今生,从onclick到await/async

    javascript与异步编程 为了避免资源管理等复杂性的问题, javascript被设计为单线程的语言,即使有了html5 worker,也不能直接访问dom. javascript 设计之初是为 ...

随机推荐

  1. HDU 5253 连接的管道 (最小生成树)

    连接的管道 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submi ...

  2. codeforces 434B B. Nanami's Digital Board(分治)

    题目链接: B. Nanami's Digital Board time limit per test 1 second memory limit per test 256 megabytes inp ...

  3. codeforces 676B B. Pyramid of Glasses(模拟)

    题目链接: B. Pyramid of Glasses time limit per test 1 second memory limit per test 256 megabytes input s ...

  4. 总结——visibility和display

    最近工作中用到了显示和隐藏——visibility和display,它们两个都有显示隐藏的意思,但是又有所差别,接下来我们先看一下效果吧. 当没有效果的时候,我们展示一下源码 <!DOCTYPE ...

  5. 每天一道LeetCode--409 .Longest Palindrome

    Given a string which consists of lowercase or uppercase letters, find the length of the longest pali ...

  6. Oracle 学习笔记3:新建数据库没有scott用户解决办法

    新建一个数据库,若选择Oracle组件时,没有选择实例方案,完成后进行口令管理,默认列表中是找不到scott用户解锁的.若要解锁scott用户,可以进行如下操作: 使用system或者sys连接数据库 ...

  7. JAVA之执行cmd命令

    感言在前:时隔好久没有更新博客园了,忙东忙西也没忙出个什么之所以然来.回首过去一两个月,只能用“疲倦”两个字来形容,而且是身心疲惫.每天11.12个小时的工作我都没觉得烦,但是总是想克服却又很难克服的 ...

  8. 经典算法系列--kmp

    前言 之前对kmp算法虽然了解它的原理,即求出P0···Pi的最大相同前后缀长度k:但是问题在于如何求出这个最大前后缀长度呢?我觉得网上很多帖子都说的不是很清楚,总感觉没有把那层纸戳破,后来翻看算法导 ...

  9. C#如何关闭一个窗口的同时打开另一个窗口

    在.net的WinForm程序中,如果是直接起动的Form作为主窗口,那么这个主窗口是不能关闭的,因为它维护了一个Windows消息循环,它一旦关闭了就等于声明整个应用程序结束,所以新打开的窗口也就被 ...

  10. 【原创】Oracle函数中对于NO_DATA_FOUND异常处理的研究

    一直以来有一个困惑,一直没解决,昨天一哥们问我这个问题,决心弄清楚,终于得到了答案.先看下面这个函数: create or replace function fn_test(c_xm varchar) ...