Javascript 异步编程的4种方法

因为 JavaScript 语言的执行环境是“单线程”(single thread)。

所谓“单线程”,就是指只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
这种模式的好处是实现起来比较简单,执行环境比较单纯;坏处是只要有一个任务耗时很长,后面的任务必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段 JavaScript 代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

为了解决这个问题,JavaScript 语言将任务的执行模式分为两种:同步(Synchronize)和异步(Asynchronous)。
“同步模式”就上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;
“异步模式”则完全不同,每个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
“异步模式”非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是 Ajax 操作。在服务器端,“异步模式”甚至是唯一的模式,因为执行环境时候单线程,如果允许同步执行所有 http 请求,服务器性能会急剧下降,很快就会失去响应。

下面总结了“异步模式”变成的4种方法:
<br />

1.回调函数

这是异步编程最基本的方法。
假定有两个函数 f1 和 f2,后者等待前者的执行结果。

f1();
f2();

如果 f1 是个很耗时的任务,可以考虑改写 f1, 把 f2 写成 f1 的回调函数。

function f1(callback){
  setTimeout(function(){
    //f1 的任务代码
    callback();
},1000);
}

执行代码就变成下面这样:

f1(f2);

采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
回调函数的有点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。

示例1
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Callback</title>
</head>
<body>
<script>
    function f1(callback){
       setTimeout(function(){
           console.log("F1 is working now");
           callback();
    },1000);
}
    function f2(){
        console.log("F2 is working now.");
    }
    f1(f2);
</script>
</body>
</html>

效果图如下:



<br />

2.事件监听

另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
还是以 f1 和 f2 为例。首先,为 f1绑定一个事件(这里采用的 jQuery 的写法)。

f1.on('done',f2);

上面这行代码的意思是,当 f1 发生 done 事件,就执行 f2。然后,对 f1 进行改写:

function f1(){
  setTimeout(function(){
    //f1 的任务代码
    f1.trigger('done');
  },1000);
}

f1.trigger('done') 表示,任务代码执行完成后,立即触发 done 事件,从而开始执行 f2。
这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以“去耦合”(Decoupling),有利于实现模块化。缺点是整个程序都变成事件驱动型,运行流程会变得很不清晰。

示例2
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Event</title>
    <script src="../js/jquery-1.11.3.min.js"></script>
</head>
<body>
<script>
    function f1(){
        setTimeout(function(){
            console.log("F1 is working now.");
            $("body").trigger('done');
        },1000);
    }
    function f2(){
        console.log("F2 is working now.");
    }
    $("body").on('done',f2);
    f1();
</script>
</body>
</html>

<br />

3.发布/订阅

上面所说的“事件”,完全可以理解成“信号”。
我们假定,存在一个“信号中心”,某个执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做“发布/订阅模式”(publish-subscribe pattern),又称“观察者模式”(observer pattern)。
这种模式有多种实现,下面采用的是 Ben Alman 的 Tiny Pub/Sub,这是 jQuery 的一个插件。

首先,f2 向“信号中心” jQuery订阅 “done”信号。

jQuery.subscribe("done",f2);

然后, f1 进行如下改写:

function f1(){
  setTimeout(function(){
    //f1 的任务代码
    jQuery.publish("done");
  },1000);
}

jQuery.publish("done") 的意思是,f1 执行完成后,向“信号中心” jQuery 发布“done”信号,从而引发 f2 的执行。
此外,f2 完成执行后,也可以取消订阅(unsubscribe)。

jQuery.unsubscribe("done",f2);

这种方法的性质与“事件监听”类似,但是明显优于后者。因为我们可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

示例3
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Publish-Subscribe Pattern</title>
    <script src="../js/jquery-1.11.3.min.js"></script>
    <script src="../js/ba-tiny-pubsub.min.js"></script>
</head>
<body>
<script>
    function f1(){
        setTimeout(function(){
            console.log("F1 is working now.");
            $.publish("done");
        },1000);
    }
    function f2(){
        console.log("F2 is working now.");
        //$.unsubscribe("done",f2);
    }
    $.subscribe("done",f2);
    f1();
</script>
</body>
</html>

<br />

4.Promises 对象

Promises 对象是 CommonJS 工作组提出的一种规范,目的是为异步编程提供统一接口。
简单说,它的思想是,每一个异步任务返回一个 Promise 对象,该对象有一个 then 方法,允许指定回调函数。比如,f1 的回调函数f2 ,可以写成:

f1().then(f2);

f1 要进行如下改写(这里使用的是 jQuery 的实现):

function f1(){
  var dfd = $.Deferred();
  setTimeout(function(){
   //f1 的任务代码
    dfd.resolve();
  },1000);
  return dfd.promise;
}

这样写的优点在于,回调函数变成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以实现许多强大的功能。
比如,指定多个回调函数:

f1().then(f2).then(f3);

比如,指定发送错误时的回调函数:

f1().then(f2).fail(f3);

而且,它还有一个前三种方法都没有的好处:如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。所以,不用担心是否错过了某个事件或者信号。这种方法的缺点就是编写和理解,都相对比较难。

示例4
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Promises Object</title>
    <script src="../js/jquery-1.11.3.min.js"></script>
</head>
<body>
<script>
    function f1(){
        var dfd = $.Deferred();
        setTimeout(function(){
            console.log("F1 is working now.");
            dfd.resolve();
        },1000);
        return dfd.promise();
    }
    function f2(){
        console.log("F2 is working now.");
    }
    f1().then(f2);
</script>
</body>
</html>

<br />

5.参考链接

<br />
来源:Javascript异步编程的4种方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 你可能知道,Javascript语言的执行环境是"单线程"(single thread)。所谓"单线程",就是指一...
    乖乖果效36阅读 147评论 0 0
  • Javascript语言的执行环境是"单线程"(single thread)。 所谓"单线程",就是指一次只能完成...
    小翼_b998阅读 204评论 0 0
  • 你可能知道,Javascript语言的执行环境是"单线程"(single thread)。所谓"单线程",就是指一...
    wyude阅读 285评论 0 2
  • 我亲爱的笑笑小宝, 此时你在门内SBS,我在门外等你。新一天的陪伴开始了。 昨晚你自言自语地说:“上个星期SBS请...
    Graciegu阅读 218评论 0 1
  • 文/青峰 雨在不停地下 一个流浪的乞丐 栖息在桥洞下 倾听行人回家的脚步声 雨水从眼中流下 静静咀嚼雨滴下的寂静 ...
    罗布泊的小哆啦阅读 115评论 0 0