JavaScript的优势之一是其如何处理异步代码。异步代码会被放入一个事件队列,等到所有其他代码执行后才进行,而不会阻塞线程。然而,对于初学者来说,书写异步代码可能会比较困难。而在这篇文章里,我将会消除你可能会有的任何困惑。

理解异步代码

JavaScript最基础的异步函数是setTimeoutsetInterval。setTimeout会在一定时间后执行给定的函数。它接受一个回调函数作为第一参数和一个毫秒时间作为第二参数。以下是用法举例:

1
2
3
4
5
6
7
8
9
10
11
console.log( "a" );
setTimeout(function() {
    console.log( "c" )
}, 500 );
setTimeout(function() {
    console.log( "d" )
}, 500 );
setTimeout(function() {
    console.log( "e" )
}, 500 );
console.log( "b" );

正如预期,控制台先输出“a”、“b”,大约500毫秒后,再看到“c”、“d”、“e”。我用“大约”是因为setTimeout事实上是不可预知的。实际上,甚至 HTML5规范都提到了这个问题:

“这个API不能保证计时会如期准确地运行。由于CPU负载、其他任务等所导致的延迟是可以预料到的。”

加壹
翻译于 3年前

6人顶

 

 翻译的不错哦!

有趣的是,直到在同一程序段中所有其余的代码执行结束后,超时才会发生。所以如果设置了超时,同时执行了需长时间运行的函数,那么在该函数执行完成之前,超时甚至都不会启动。实际上,异步函数,如setTimeout和setInterval,被压入了称之为Event Loop的队列。

Event Loop是一个回调函数队列。当异步函数执行时,回调函数会被压入这个队列。JavaScript引擎直到异步函数执行完成后,才会开始处理事件循环。这意味着JavaScript代码不是多线程的,即使表现的行为相似。事件循环是一个先进先出(FIFO)队列,这说明回调是按照它们被加入队列的顺序执行的。JavaScript被 node选做为开发语言,就是因为写这样的代码多么简单啊。 

加壹
翻译于 3年前

2人顶

 

 翻译的不错哦!

Ajax

异步Javascript与XML(AJAX)永久性的改变了Javascript语言的状况。突然间,浏览器不再需要重新加载即可更新web页面。 在不同的浏览器中实现Ajax的代码可能漫长并且乏味;但是,幸亏有jQuery(还有其他库)的帮助,我们能够以很容易并且优雅的方式实现客户端-服务器端通讯。

我们可以使用jQuery跨浏览器接口$.ajax很容易地检索数据,然而却不能呈现幕后发生了什么。比如:

1
2
3
4
5
6
7
8
9
10
var data;
$.ajax({
    url: "some/url/1",
    success: function( data ) {
        // But, this will!
        console.log( data );
    }
})
// Oops, this won't work...
console.log( data );

较容易犯的错误,是在调用$.ajax之后马上使用data,但是实际上是这样的:

1
2
3
4
5
6
7
xmlhttp.open( "GET""some/ur/1"true );
xmlhttp.onreadystatechange = function( data ) {
    if ( xmlhttp.readyState === 4 ) {
        console.log( data );
    }
};
xmlhttp.send( null );

底层的XmlHttpRequest对象发起请求,设置回调函数用来处理XHR的readystatechnage事件。然后执行XHR的send方法。在XHR运行中,当其属性readyState改变时readystatechange事件就会被触发,只有在XHR从远端服务器接收响应结束时回调函数才会触发执行。

jinker
翻译于 3年前

1人顶

 

 翻译的不错哦!

处理异步代码

异步编程很容易陷入我们常说的“回调地狱”。因为事实上几乎JS中的所有异步函数都用到了回调,连续执行几个异步函数的结果就是层层嵌套的回调函数以及随之而来的复杂代码。

node.js中的许多函数也是异步的。因此如下的代码基本上很常见:

1
2
3
4
5
6
7
8
9
10
var fs = require( "fs" );
fs.exists( "index.js"function() {
    fs.readFile( "index.js""utf8"function( err, contents ) {
        contents = someFunction( contents ); // do something with contents
        fs.writeFile( "index.js""utf8"function() {
            console.log( "whew! Done finally..." );
        });
    });
});
console.log( "executing..." );

下面的客户端代码也很多见:

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
GMaps.geocode({
    address: fromAddress,
    callback: function( results, status ) {
        if ( status == "OK" ) {
            fromLatLng = results[0].geometry.location;
            GMaps.geocode({
                address: toAddress,
                callback: function( results, status ) {
                    if ( status == "OK" ) {
                        toLatLng = results[0].geometry.location;
                        map.getRoutes({
                            origin: [ fromLatLng.lat(), fromLatLng.lng() ],
                            destination: [ toLatLng.lat(), toLatLng.lng() ],
                            travelMode: "driving",
                            unitSystem: "imperial",
                            callback: function( e ){
                                console.log( "ANNNND FINALLY here's the directions..." );
                                // do something with e
                            }
                        });
                    }
                }
            });
        }
    }
});

Nested callbacks can get really nasty, but there are several solutions to this style of coding.

嵌套的回调很容易带来代码中的“坏味道”,不过你可以用以下的几种风格来尝试解决这个问题

The problem isn’t with the language itself; it’s with the way programmers use the language — Async Javascript.

没有糟糕的语言,只有糟糕的程序猿 ——异步JavaSript

QiNark
翻译于 3年前

3人顶

 

 翻译的不错哦!

命名函数

清除嵌套回调的一个便捷的解决方案是简单的避免双层以上的嵌套。传递一个命名函数给作为回调参数,而不是传递匿名函数:

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
29
30
var fromLatLng, toLatLng;
var routeDone = function( e ){
    console.log( "ANNNND FINALLY here's the directions..." );
    // do something with e
};
var toAddressDone = function( results, status ) {
    if ( status == "OK" ) {
        toLatLng = results[0].geometry.location;
        map.getRoutes({
            origin: [ fromLatLng.lat(), fromLatLng.lng() ],
            destination: [ toLatLng.lat(), toLatLng.lng() ],
            travelMode: "driving",
            unitSystem: "imperial",
            callback: routeDone
        });
    }
};
var fromAddressDone = function( results, status ) {
    if ( status == "OK" ) {
        fromLatLng = results[0].geometry.location;
        GMaps.geocode({
            address: toAddress,
            callback: toAddressDone
        });
    }
};
GMaps.geocode({
    address: fromAddress,
    callback: fromAddressDone
});

此外, async.js 库可以帮助我们处理多重Ajax requests/responses. 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async.parallel([
    function( done ) {
        GMaps.geocode({
            address: toAddress,
            callback: function( result ) {
                done( null, result );
            }
        });
    },
    function( done ) {
        GMaps.geocode({
            address: fromAddress,
            callback: function( result ) {
                done( null, result );
            }
        });
    }
], function( errors, results ) {
    getRoute( results[0], results[1] );
});

这段代码执行两个异步函数,每个函数都接收一个名为"done"的回调函数并在函数结束的时候调用它。当两个"done"回调函数结束后,parallel函数的回调函数被调用并执行或处理这两个异步函数产生的结果或错误。

ITgo
翻译于 3年前

1人顶

 

 翻译的不错哦!

Promises模型

引自 CommonJS/A

promise表示一个操作独立完成后返回的最终结果。

有很多库都包含了promise模型,其中jQuery已经有了一个可使用且很出色的promise API。jQuery在1.5版本引入了Deferred对象,并可以在返回promise的函数中使用jQuery.Deferred的构造结果。而返回promise的函数则用于执行某种异步操作并解决完成后的延迟。

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
29
30
var geocode = function( address ) {
    var dfd = new $.Deferred();
    GMaps.geocode({
        address: address,
        callback: function( response, status ) {
            return dfd.resolve( response );
        }
    });
    return dfd.promise();
};
var getRoute = function( fromLatLng, toLatLng ) {
    var dfd = new $.Deferred();
    map.getRoutes({
        origin: [ fromLatLng.lat(), fromLatLng.lng() ],
        destination: [ toLatLng.lat(), toLatLng.lng() ],
        travelMode: "driving",
        unitSystem: "imperial",
        callback: function( e ) {
            return dfd.resolve( e );
        }
    });
    return dfd.promise();
};
var doSomethingCoolWithDirections = function( route ) {
    // do something with route
};
$.when( geocode( fromAddress ), geocode( toAddress ) ).
    then(function( fromLatLng, toLatLng ) {
        getRoute( fromLatLng, toLatLng ).then( doSomethingCoolWithDirections );
    });

这允许你执行两个异步函数后,等待它们的结果,之后再用先前两个调用的结果来执行另外一个函数。

promise表示一个操作独立完成后返回的最终结果。

在这段代码里,geocode方法执行了两次并返回了一个promise。异步函数之后执行,并在其回调里调用了resolve。然后,一旦两次调用resolve完成,then将会执行,其接收了之前两次调用geocode的返回结果。结果之后被传入getRoute,此方法也返回一个promise。最终,当getRoute的promise解决后,doSomethingCoolWithDirections回调就执行了。

加壹
翻译于 3年前

2人顶

 

 翻译的不错哦!

事件

事件是另一种当异步回调完成处理后的通讯方式。一个对象可以成为发射器并派发事件,而另外的对象则监听这些事件。这种类型的事件处理方式称之为 观察者模式 。 backbone.js 库在withBackbone.Events中就创建了这样的功能模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var SomeModel = Backbone.Model.extend({
   url: "/someurl"
});
var SomeView = Backbone.View.extend({
    initialize: function() {
        this.model.on( "reset"this.render, this );
        this.model.fetch();
    },
    render: function( data ) {
        // do something with data
    }
});
var view = new SomeView({
    model: new SomeModel()
});

还有其他用于发射事件的混合例子和函数库,例如 jQuery Event Emitter , EventEmitter , monologue.js ,以及node.js内建的 EventEmitter 模块。

事件循环是一个回调函数的队列。

一个类似的派发消息的方式称为 中介者模式 , postal.js 库中用的即是这种方式。在中介者模式,有一个用于所有对象监听和派发事件的中间人。在这种模式下,一个对象不与另外的对象产生直接联系,从而使得对象间都互相分离。

加壹
翻译于 3年前

1人顶

 

 翻译的不错哦!

绝不要返回promise到一个公用的API。这不仅关系到了API用户对promises的使用,也使得重构更加困难。不过,内部用途的promises和外部接口的事件的结合,却可以让应用更低耦合且便于测试。

在先前的例子里面,doSomethingCoolWithDirections回调函数在两个geocode函数完成后执行。然后,doSomethingCoolWithDirections才会获得从getRoute接收到的响应,再将其作为消息发送出去。

1
2
3
4
5
var doSomethingCoolWithDirections = function( route ) {
    postal.channel( "ui" ).publish( "directions.done",  {
        route: route
    });
};

这允许了应用的其他部分不需要直接引用产生请求的对象,就可以响应异步回调。而在取得命令时,很可能页面的好多区域都需要更新。在一个典型的jQuery Ajax过程中,当接收到的命令变化时,要顺利的回调可能就得做相应的调整了。这可能会使得代码难以维护,但通过使用消息,处理UI多个区域的更新就会简单得多了。

1
2
3
4
5
6
7
8
var UI = function() {
    this.channel = postal.channel( "ui" );
    this.channel.subscribe( "directions.done"this.updateDirections ).withContext( this );
};
UI.prototype.updateDirections = function( data ) {
    // The route is available on data.route, now just update the UI
};
app.ui = new UI();

另外一些基于中介者模式传送消息的库有 amplifyPubSubJS, and radio.js。 

加壹
翻译于 3年前

1人顶

 

 翻译的不错哦!

结论

JavaScript 使得编写异步代码很容易. 使用 promises, 事件, 或者命名函数来避免“callback hell”. 为获取更多javascript异步编程信息,请点击Async JavaScript: Build More Responsive Apps with Less. 更多的实例托管在github上,地址NetTutsAsyncJS,赶快Clone吧 !

基于事件的 JavaScript 编程:异步与同的更多相关文章

  1. JavaScript编程异步助手:Promise

    异步模式在Web编程中变得越来越重要,对于Web主流语言JavaScript来说,这种模式实现起来不是很利索,为此,许多JavaScript库(比如 jQuery和Dojo.AngularJS)添加了 ...

  2. 二、基于事件的异步编程模式(EAP)

    一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式--APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题--不支持对异步操作的取消和没有提供对进 ...

  3. 异步编程(二)基于事件的异步编程模式 (EAP)

    一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式——APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题——不支持对异步操作的取消和没有提供对进 ...

  4. javascript的异步编程

    同步与异步 介绍异步之前,回顾一下,所谓同步编程,就是计算机一行一行按顺序依次执行代码,当前代码任务耗时执行会阻塞后续代码的执行. 同步编程,即是一种典型的请求-响应模型,当请求调用一个函数或方法后, ...

  5. Monad、Actor与并发编程--基于线程与基于事件的并发编程之争

    将线程.事件.状态等包装成流的源. 核心:解决线程的消耗和锁的效率问题. Java和Node.js可以说分别是基于线程和基于事件的两个并发编程代表,它们互相指责瞧不起对方,让我们看看各种阵营的声音: ...

  6. .NET - 基于事件的异步模型

    注:这是大概四年前写的文章了.而且我离开.net领域也有四年多了.本来不想再发表,但是这实际上是Active Object模式在.net中的一种重要实现方法,因此我把它掏出来发布一下.如果该模型有新的 ...

  7. 基于事件的异步模式(EAP)

    什么是EAP异步编程模式 EAP基于事件的异步模式是.net 2.0提出来的,实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法和对应的Completed事件,并且这些类都支持异步 ...

  8. Event-based Asynchronous Pattern Overview基于事件的异步模式概览

    https://msdn.microsoft.com/zh-cn/library/wewwczdw(v=vs.110).aspx Applications that perform many task ...

  9. 在Silverlight中的DispatcherTimer的Tick中使用基于事件的异步请求

    需求:在silverlight用户界面上使用计时器定时刷新数据. 在 Silverlight 中的 DispatcherTimer 的 Tick 事件 中使用异步请求数据时,会出现多次请求的问题,以下 ...

随机推荐

  1. ssm之mapper异常 Result Maps collection already contains value for com.ssj.mapper.UserMapper 和 Type interface com.ssj.mapper.UserMapper is already known to the MapperRegistry.

    异常一:Result Maps collection already contains value for com.ssj.mapper.XXXMapper 原因分析:XXXmapper.xml文件中 ...

  2. Java 7 新增功能

    Java 7 新增功能如下: 对二进制整数的支持,以0b或0B开头. 在数值中可以使用下划线,不管是整型数值,还是浮点型数值,都可以自由地使用下划线,这样可以直观地分辨数值常量中到底包含多少位.如:3 ...

  3. memento模式

    参考资料 • 维基百科:https://en.wikipedia.org/wiki/Memento_pattern • 百度百科:http://baike.baidu.com/link?url=ZQZ ...

  4. 97. Interleaving String(字符串的交替连接 动态规划)

    Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. For example,Given:s1 = ...

  5. 粗略介绍Java AQS的实现原理

    本文转自 http://www.importnew.com/24006.html 感谢作者 对我很有帮助 ①引言 AQS是JDK1.5提供的一个基于FIFO等待队列一个同步器的基础框架,java中的同 ...

  6. Linux centos7安装Mongodb

    Linux centos7安装Mongodb-4.0 1.下载 官方下载地址:https://www.mongodb.com/download-center/community 2.上传解压 1)rz ...

  7. JS答辩习题

    php高中班javascript答辩题目 1 什么是javascript?  答:Javascript是一种专门设计用来增强网页交互性的脚本语言,它也是一种解释型语言. 2 Javascript与C语 ...

  8. MBR主引导记录

    LBA的寻址方式可以让我们支持2TB,这是因为分区相对起始扇区号(分区项08-11个字节)和分区最大扇区数(分区项12-15个字节)的位数都是32bit.也就是0xFFFFFFFF*512/1024/ ...

  9. 20145312 《Java程序设计》第五周学习总结

    20145312 <Java程序设计>第五周学习总结 学习笔记 Chapter8 异常处理 8.1语法与继承架构 1.Java中的错误以对象方式呈现,只要捕捉包装错误的对象,就可以针对该错 ...

  10. 20135302魏静静——linux课程第四周实验及总结

    linux课程第四周实验及总结 一.实验 我选择的是第20号系统调用,getpid 代码如下: /* getpid.c */ #include <unistd.h> #include &l ...