Javascript异步编程的4种方法

作者: 阮一峰

日期: 2012年12月21日

你可能知道,Javascript语言的执行环境是"单线程"(single thread)。

所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。

这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

"异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

本文总结了"异步模式"编程的4种方法,理解它们可以让你写出结构更合理、性能更出色、维护更方便的Javascript程序。

一、回调函数

这是异步编程最基本的方法。

假定有两个函数f1和f2,后者等待前者的执行结果。

  f1();

  f2();

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

  function f1(callback){

    setTimeout(function () {

      // f1的任务代码

      callback();

    }, 1000);

  }

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

  f1(f2);

采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。

回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。

二、事件监听

另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

还是以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),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。

三、发布/订阅

上一节的"事件",完全可以理解成"信号"。

我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(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);

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

四、Promises对象

Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口

简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如,f1的回调函数f2,可以写成:

  f1().then(f2);

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

  function f1(){

    var dfd = $.Deferred();

    setTimeout(function () {

      // f1的任务代码

      dfd.resolve();

    }, 500);

    return dfd.promise;

  }

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

比如,指定多个回调函数:

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

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

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

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

五、参考链接

  * Asynchronous JS: Callbacks, Listeners, Control Flow Libs and Promises

(完)

留言(97条)

特别推荐一下国产的continuation.js,可以将复杂的回调函数展平。

https://github.com/BYVoid/continuation

回调函数的方法,会遇到“回调金字塔”的问题,比如


var err, stats, obj;
setTimeout(function () {
fs.lstat('/path', function () {
err = arguments[0];
stats = arguments[1];
fs.readdir('/path', function () {
err = arguments[0];
obj.files = arguments[1];
});
});
}, 1000);

而是用continuation.js就可以这样写


function textProcessing(ret) {
fs.readFile('somefile.txt', 'utf-8', cont(err, contents));
if (err) return ret(err);
contents = contents.toUpperCase();
fs.readFile('somefile2.txt', 'utf-8', cont(err, contents2));
if (err) return ret(err);
contents += contents2;
fs.writeFile('somefile_concat_uppercase.txt', contents, cont(err));
if (err) return ret(err);
ret(null, contents);
}
textProcessing(cont(err, contents));
if (err)
console.error(err);

事件监听 和发布/订阅 内部实现,原理是一样的。发布/订阅其实也是事件。

引用小铱的发言:
特别推荐一下国产的continuation.js,可以将复杂的回调函数展平。

在 Node.js 的开发中,由于逻辑分层所致,会出现多层回调。易于引发异常处理混乱、闭包过于复杂、代码难以维护等问题。
没有了解过 continuation.js 项目,不过国内 Jeffery Zhao 的项目 Jscex 做了一项有意思的事,就是使用一定的技巧将异步后续步骤的蹩脚语法整理成常见方式。
项目开源地址:https://github.com/JeffreyZhao/jscex

引用陈计节的发言:

在 Node.js 的开发中,由于逻辑分层所致,会出现多层回调。易于引发异常处理混乱、闭包过于复杂、代码难以维护等问题。
没有了解过 continuation.js 项目,不过国内 Jeffery Zhao 的项目 Jscex 做了一项有意思的事,就是使用一定的技巧将异步后续步骤的蹩脚语法整理成常见方式。
项目开源地址:https://github.com/JeffreyZhao/jscex



Jscex现在已经改名为windjs。continuation.js和那个有点相似,都是基于js解析编译的新语法。这种方法虽然很酷不过是在难以让人放心,想不起来还是promise和async.js这种平坦化方式更让人放心一点。

你的配图并不十分准确. 配图里面是同步无并发执行和并发执行的区别. 在JavaScript中, 是无法进行并发执行的. 所有的例子中, 没有任何两个函数或者两段代码是同时执行的.

JavaScript可以通过快速的异步执行切换来模拟并发.

引用lixiong的发言:

你的配图并不十分准确. 配图里面是同步无并发执行和并发执行的区别. 在JavaScript中, 是无法进行并发执行的. 所有的例子中, 没有任何两个函数或者两段代码是同时执行的.

JavaScript可以通过快速的异步执行切换来模拟并发.

配图没错,那是loading资源,确是是并发的。没有并发的是loading之后的callback。

@lixiong:

配图改过了,现在应该准确一点了。

第二种不一定比第三种好或者差,当订阅者到达一定数量时发布中心承受的压力是需要考虑的,全局消息充斥整个程序也见得很好

异步的实现就是用定时器函数改变代码的执行顺序吗?

引用hafeyang的发言:

事件监听 和发布/订阅 内部实现,原理是一样的。发布/订阅其实也是事件。

是的,自定义事件和发布/订阅模式的实现代码一模一样,只不过换了个叫法而已,
《JavaScript程序员参考手册》中就有相关实现的代码。

这篇文章不错,浅显易懂。
实际上用任何语言进行异步处理都要面临相同的问题。原理才是重要的。

发布/订阅模式,还能携带参数和返回值。

var invokeMapping = {}, addInvokeListener = function addInvokeListener (opCode, func) {
(invokeMapping[opCode] = invokeMapping[opCode] || []).push(func);
}, invokeAndWait = function invokeAndWait (opCode, inParam, outParam) {
var listeners = invokeMapping[opCode];
if (listeners) {
listeners.forEach(function (func, index) {
func(inParam, outParam);
});
}
};

//使用方法:
addInvokeListener("hello", function (inParam, outParam) {
alert(inParam);
outParam.result = "success";
});
var ret = {};
invokeAndWait("hello", "hello world", ret);
alert(ret.result); // 输出success

引用小涛的发言:

异步的实现就是用定时器函数改变代码的执行顺序吗?

这只是异步编程,并不是异步执行,
我的理解,javascript应该只有单线程,即使使用了异步编程,在执行层面上还是一步一步执行的,只是会随时有插队的而已

在一个系统中有多个被监听对象时,订阅/发布(观察者模式)确实比监听模式可以集中管理事件处理,事件的触发也可以做成统一模型,程序的结构更加清晰。

不管怎样,观察者模式是监听模式的高级演进版,监听模式是基础。

当需要监听的对象只有一两个时,就没有必要再使用观察者模式了,使用反而增加了系统的复杂度。

根据java jdk的api,监听模式至少要实现两个接口java.util.EventListener和java.util.Event,Event的构造函数里有一个保留事件发生点的钩子(对象),用于在监听对象里回调钩子相关的东西。

我看作者给出的例子都比较简单,这里给出js监听模式的具体实现,仅供大家讨论。

// 事件对象
var Event = function(obj) {
this.obj = obj;
this.getSource = function() {
return this.obj;
}
}

// 监听对象
var F2 = function() {
this.hander = function(event) {
var f2 = event.getSource();
console.log("f2 do something!");
f2.callback();
}
}

// 被监听对象
var F1 = function() {
this.abc = function() {
console.log("f1 do something one!");
// 创建事件对象
var e = new Event(this);
// 发布事件
this.f2.hander(e);
console.log("f1 do something two!");
}

this.on = function(f2) {
this.f2 = f2;
}

this.callback = function() {
console.log("f1 callback invoke!");
}
}

// 主函数
function main() {
var f1 = new F1();
var f2 = new F2();
// 加入监听
f1.on(f2);
f1.abc();
}

// 运行主函数
main();

执行结果是:


f1 do something one!
f2 do something!
f1 callback invoke!
f1 do something two!

我自认为是个经验比较丰富的web开发人员,但是一般情况下用setTimeout就完了。没发现平时的开发中需要这么复杂的异步操作。

其实异步也是用ajax比较多,ajax是你阐述的异步编程中的一种吗。

我来分享一个自己实现的基于事件驱动的单线程异步框架 :http://www.oschina.net/code/snippet_103999_14286

欢迎拍砖

异步编程的关键是切分任务,并且保证每个任务足够小,执行得足够快。注意,任务能被快速执行是关键中的关键。因为在单线程环境中,只要有一个任务的执行时间稍长,就会导致排在后面的任务被延后。

楼主提到将

f1();
f2();

改成

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

便不会阻塞程序的执行的说法有失偏颇。

因为当执行f1的任务代码时, setTimeout的回调函数:

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

会独占当前的执行线程, 直至执行完毕;除非虚拟机(或运行时)允许基于单线程的多任务(函数)抢占式调度(即一个函数执行了一半时被中断,切换到另一个函数中执行)。至少目前nodejs不支持所谓的基于单线程的多任务(函数)抢占式调度


个人认为,不管是上述哪四种方式来实现异步,原理都是一样的。那就是有一个任务队列存放待执行的任务,一个工作线程不断从任务队列中取出任务然后执行;另外还需要一个事件监控线程来捕捉事件并把事件封装成新的任务放入任务队列中。


js 的异步其更多的是为了和界面UI 的分时,所以尽量将任务切割为细粒度的小任务,每次执行完一段小任务只耗费一段足够小的时间然后让出时间给UI,这样UI就不会卡死。如果任务切割得不好,任务段仍然耗时长,那么无论使什么花样,一样卡死UI。因为js的异步实现不了真正并行。

我用IE9 查看你的主页时,里面的链接一个都不能点击;刷新了几次仍然不能点击,换成火狐浏览器 就一切正常了。

@路人:

我都没发觉……这是因为加了雪花特效,现在取消了。

其实还是单线程执行,只是改变了顺序,将执行时间长的代码片断(假设为片断A)给延迟执行了
但...如果某个函数的执行必须基于这个片断A的执行结果,那异步编程也就无意义了

你好阮老师:
1.回调函数,如下这么写难道不是等“f1的任务代码”执行完再执行callback()吗?
这还是同步呀。
f1(f2);
function f1(callback){

    setTimeout(function () {

      // f1的任务代码

      callback();

    }, 1000);

  }

to Evan
"同步或异步"要从调用方来判断。

同步函数返回表示工作已完成或发生异常,接下来依赖此结果的的代码被调用方继续执行。

异步函数返回表示工作已发布出去,什么时候完成不知道,接下来的代码无法依赖一个未知的结果,所以要传入一个回调在完成后处理。

我感觉js的异步就是两种:回调和观察者模式。监听器的方式也是观察者模式的一种实现,Promises对象只是个语法糖而已。
js是单线程的,异步也不能加快程序运行 .

引用huangyunbin的发言:

我感觉js的异步就是两种:回调和观察者模式。监听器的方式也是观察者模式的一种实现,Promises对象只是个语法糖而已。
js是单线程的,异步也不能加快程序运行 .

同意js的异步就是回调和观察者模式两种的观点。

发现这里的文章还不适合我这种刚学习JS的,看看先,话说留言板很干净,是怎么实现的啊

个人感觉第一个例子总让人产生歧义,我一开始以为你想说明f1()很耗时,为了不阻塞f2(),所以采用回调的方式来解决这个问题。而你这没有解决这个问题,而是将f1()推迟执行,没有阻塞其他逻辑(不包括f2())......总感觉答非所问,提出的问题和解决的问题不一致

很详细,差不多懂了。

貌似有点儿涩

回调函数不一定是异步的,第一个例子异步执行不是因为f2作为回调函数,而是setTimeout促发的异步执行

异步会有暴露过多接口的问题

四、Promises对象=> return dfd.promise; 少写了一对括号=》 return dfd.promise();

第一个callback例子:
采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
我想来想去都感觉应该是:防止f2堵塞程序运行,而不是防f1堵塞。而f1本身就如同ajax这类异步请求,f2依赖f1的结果。 是这么理解么?

引用hafeyang的发言:

事件监听 和发布/订阅 内部实现,原理是一样的。发布/订阅其实也是事件。

说成事件监听是发布/订阅的一个特殊例子更好

引用canon的发言:

个人感觉第一个例子总让人产生歧义,我一开始以为你想说明f1()很耗时,为了不阻塞f2(),所以采用回调的方式来解决这个问题。而你这没有解决这个问题,而是将f1()推迟执行,没有阻塞其他逻辑(不包括f2())......总感觉答非所问,提出的问题和解决的问题不一致

同感。但保留对阮老的敬意。

引用holly的发言:

四、Promises对象=> return dfd.promise; 少写了一对括号=》 return dfd.promise();

太感谢了!

总是这么通俗易懂,言简意赅,牛!

阮老师,第一个例子就不是很理解

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

这样的话f1的任务代码和callback不依然是顺序执行吗?如何做到了异步执行?

引用larz的发言:

to Evan
"同步或异步"要从调用方来判断。

同步函数返回表示工作已完成或发生异常,接下来依赖此结果的的代码被调用方继续执行。

异步函数返回表示工作已发布出去,什么时候完成不知道,接下来的代码无法依赖一个未知的结果,所以要传入一个回调在完成后处理。

文中的写法貌似依然是同步顺序执行的

发布/订阅 文中说的那个插件也是用on trigger 实现的。。。。

引用canon的发言:

个人感觉第一个例子总让人产生歧义,我一开始以为你想说明f1()很耗时,为了不阻塞f2(),所以采用回调的方式来解决这个问题。而你这没有解决这个问题,而是将f1()推迟执行,没有阻塞其他逻辑(不包括f2())......总感觉答非所问,提出的问题和解决的问题不一致

我以为应该只是放setimeout放在最上端,里面放进f2,然后才执行f1

当f1执行过久,f2自动先被执行,无需等待f1,当f1比预期早执行完,clear setimeout,执行f2...
我平常好像是这样写的。。。

第一种顶多算伪异步,真正的异步是程序执行期间不会冻结UI的,比如AJAX的异步操作

javaScript 是单线程运行机制,无论同步和异步最终还是单线程的,异步只是把代码运行时间长的延迟了,被延迟的时间里去执行其他代码而已。

第一个例子,
function f1(callback){
  setTimeout(function () {
    // f1的任务代码
    callback();
  }, 1000);
}
我抓头想了大半天,没明白,于是我自己写了个验证,发现f1还是会阻塞f2,不知您作何解释?
我的代码如下:
function a(callback){
setTimeout(function(){
//耗时操作
//-- f1 start --
var i=0;
while(i if(i++ === 999999999){
alert("终于执行完了");
}
}
//-- f1 end --
callback();
},0);
};
a(function(){
alert("立即执行");
});

如果有
f1(f2)
f3();

对比下
f1()
f2()
f3();

应该就理解异步了。

continuation 这个好用不,现在经常碰到多重callback

例如:

$(element).one($.support.transition.end,function(){
//第一步动画完成
$(element).one($.support.animation.end,function(){
//第二步动画完成
。。。。。。
。。。。
//十步动画 写得特别疼。。。。
})
})

@耗子:

我也是觉得有点问题。还是被阻塞了啊。

引用无名指的发言:

如果有
f1(f2)
f3();

对比下
f1()
f2()
f3();

应该就理解异步了。

谢谢!刚开始我也不是很理解,看到你说到后面的f3()之后理解了。

我的理解是:

假如有两个函数f1、f2、f3, 后者f2等待前者f1的执行结果.
```javascript
f1();
f2();
f3();
```
如果f1是个很耗时,可以考虑改写f1, 把f2写成f1的回调函数.
```javascript
function f1(callback) {
setTimeout(function () {
// f1的任务代码
callback();
}, 1000);
}
f1(f2);
f3();
```
采用这样的方式,f1不会堵塞后面的程序例如f3的运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。

将耗时的操作延迟执行,貌似没有什么意义。假设f3是界面,则先执行了f3此时界面能拖动,然后settimeout之后又去执行f1,此时f3不被执行,界面又卡了。

我认为所谓的异步执行,其实是settimeout所控制的那部分时间,让js在底层开辟了新的线程去做一些IO操作了。这样才实现了f3执行的时候界面不卡顿,忽然f1底层线程的IO操作完成了则执行f2,f2执行完了又回到f3.

尼玛,底层的多线程才是所谓的异步。 这些js所谓的异步编程根本都是语法而已,只有在f1函数中使用了ajax之类的底层多线程函数,才能利用起f3执行所用的那段setTimeout时间。

引用hafeyang的发言:

事件监听 和发布/订阅 内部实现,原理是一样的。发布/订阅其实也是事件。

其实这4个方法全是事件驱动了,看起来有点奇技淫巧.
实际上仔细分析,他也就是数学上那么点东西

return dfd.promise;有错,应该是return dfd.promise();

var worker = new Worker("/myJs.js");
worker.onmessage = function(e){ var message = e.data; };
worker.onerror = function(e){ var error = e.message; };
worker.postMessage("参数");

引用无名指的发言:

如果有
f1(f2)
f3();

对比下
f1()
f2()
f3();

应该就理解异步了。

这个是setTimeout的功劳吧?它会把耗时操作放到队列中啊,主线程只剩f3了,所以肯定先执行的f3

引用耗子的发言:

第一个例子,
function f1(callback){
  setTimeout(function () {
    // f1的任务代码
    callback();
  }, 1000);
}
我抓头想了大半天,没明白,于是我自己写了个验证,发现f1还是会阻塞f2,不知您作何解释?
我的代码如下:
function a(callback){
setTimeout(function(){
//耗时操作
//-- f1 start --
var i=0;
while(i
if(i++ === 999999999){
alert("终于执行完了");
}
}
//-- f1 end --
callback();
},0);
};
a(function(){
alert("立即执行");
});


你楼下的作者说的对, 因为setTimeout是一个异步方法,所以当你这样写的时候才能感觉到异步调用,
f1(f2)
f3()
你会发现f3会优先于f1(f2)执行
老师的例子确实是按顺序执行f1,f2的,觉得老师应该是语误了,没表达清楚, 不知道我理解的对不对

其实异步还是定时器发生的作用

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

"// f1的任务代码" 这个注释应该放在 setTimeout 外面吧?

第一种根本不是所谓的异步啊,异步应该是有了多线程的效果,就是同时运行多个任务(代码),貌似只有事件驱动是没有和js在同一线程的,从而实现了异步,settimeout什么的只是改变队列顺序而已。后面的模拟事件的几种方法才有可能是异步

引用korall的发言:

js 的异步其更多的是为了和界面UI 的分时,所以尽量将任务切割为细粒度的小任务,每次执行完一段小任务只耗费一段足够小的时间然后让出时间给UI,这样UI就不会卡死。如果任务切割得不好,任务段仍然耗时长,那么无论使什么花样,一样卡死UI。因为js的异步实现不了真正并行。

这个网友说的很对,之前我都理解错了,对不住老师。原来js不管怎么做都是单线程的,异步也不能开启多线程,js里的异步只是改变队列的执行顺序,要想不出现耗时代码,唯一的方法的就是把耗时代码切成小段的代码,然后用settimeout异步留出线程的空闲时间给渲染线程,这样才不会出现ui卡死的现象。
否则无论怎么做没有用,说白了,js本来就不适合做大量运算的代码,那样的代码应该用多线程的语言来写

还是以f1和f2为例。首先,为f1绑定一个事件(这里采用的jQuery的写法)。

我只想知道 jQuery 是怎么给函数绑定事件的。。。。。

promise算作观察者模式吗?

引用阿辉的发言:

第一种根本不是所谓的异步啊,异步应该是有了多线程的效果,就是同时运行多个任务(代码),貌似只有事件驱动是没有和js在同一线程的,从而实现了异步,settimeout什么的只是改变队列顺序而已。后面的模拟事件的几种方法才有可能是异步

大哥 settimeout是js中最简单的异步方法,没有多线程,如果这都不算异步那什么才算异步?阮老师的这篇文章比较早,如今对于异步编程又有了很多新的方式,比如es6中promise也成了标准语法糖,es7中基于promise又提出几种新的语法糖,还有了可以替代ajax的fetch也是基于promise,总之如今异步编程的方法已经越来越多了

@耗子:

js没有多线程的概念,只有阻塞的概念,我们说的异步,是只不阻塞后续代码的意思。如:

function a(){
//耗时操作
//-- f1 start --
var i=0;
while(i
if(i++ === 999999999){
alert("终于执行完了");
}
}
//-- f1 end --
}

function b(){
setTimeout(function(){
//耗时的a后执行
a()
})
alert("立即执行")
}
b();

这样a操作会在alert之后执行,而a不会阻塞后面alert执行。

因为alert、同步ajax、大量的while循环在执行的时候,都会阻塞线程,所以尽量把他们放到线程空闲的时候执行

回调函数中,我的理解是 callback() 要写到 setTimeout 外面。

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

f1(f2);

  function f1(){
    var dfd = $.Deferred();
    setTimeout(function () {
      // f1的任务代码
      dfd.resolve();
    }, 500);
    return dfd.promise; //这里应该写成 return dfd.promise(); 否则报 then is not a function
  }

jquery里函数怎么绑定事件的....

因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者

不知道是通过什么方式查看到这些信息的?

阮大哥,能不能再出一篇讲解js中异步和回调的文章,这篇文章里的第一个例子感觉并没有讲清楚异步执行,我看了您后面的event loop,不知道是否和这个有关,总感觉有点不得要领,希望弄懂这些原理,期待您的文章!!!

http://www.mahaixiang.cn/js/270.html
峰哥,有人盗用你的文章~

阮老师:说
是将f1 和f2 这个顺序执行的从主 worker种分出来单独执行;
利用setTimeout方法;
至于setTimeout方法是把f1和f2放在那里去执行没有说
setTimeout(function(){
f1();
f2();
},100);
与你的执行结果以及执行顺序上我觉得没有什么差异;

node目前可以说只有io等资源型异步;
我想很多人是想知道,类似async.parallel是如何实现的。
更关注f1和f2 异步并发执行
async.parallel([f1,f2],callback);

引用whws的发言:

还是以f1和f2为例。首先,为f1绑定一个事件(这里采用的jQuery的写法)。

我只想知道 jQuery 是怎么给函数绑定事件的。。。。。

我也想问这个问题,试了半天,百度了半天,居然找不到这种给函数绑定时间的方法。泪崩...

阮老师我还是不太明白, 

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

这样函数callback()不是仍然在f1后执行吗?

贡献一个例子,但是setTimeout貌似不是异步编程方法。推荐一篇文章(http://www.cnblogs.com/smght/p/4368399.html#3575993)

(function() {

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

f1(f2);

function f2() {
alert('22222')
} //这(f2)是回调函数

function f3(){
alert('33333')
}

f3()
})();

//先显示33333,点击确定等待1s后显示11111,再显示22222

单线程是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。
"异步模式"是指每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
js既然是单线程的,就是只能在一个任务结束之后才能执行下一个任务。和异步的“后一个任务不等前一个任务结束就执行”是矛盾的吗?

可以推出打赏功能,给广大受益者一个回报的渠道

“一.回调函数”那里的某句话逻辑讲得通吗?我是JS新手,求教。
那里讲到f1是耗时的操作,把f2改成f1的回调,然后下面跟了一句话:
“这样,f1不会堵塞程序运行...”
这从上下文来看,逻辑是不通的吧?因为这样看来,含义是:f2变成f1的回调后,f1就不会阻塞线程了。这明显不合理嘛。
我个人认为,作者表达的意思是否是这样:
这样,f1函数这就可以不用必须要同步调用了。也就契合ajax技术不是同步调用的具体实际。
以上理解是否正确,望指点。

其实这样的异步仅仅是推迟了某些代码的执行,对于一段需要长时间来执行的代码来说,异步只是将其的执行顺序移到后面,然后不阻塞其他的逻辑代码,等到其他代码执行完成,然后再执行这段代码,但是这段代码在执行的时候仍然还是会阻塞UI和其他业务代码的。

引用董颖的发言:

单线程是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。
"异步模式"是指每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
js既然是单线程的,就是只能在一个任务结束之后才能执行下一个任务。和异步的“后一个任务不等前一个任务结束就执行”是矛盾的吗?


引用morning的发言:

我也想问这个问题,试了半天,百度了半天,居然找不到这种给函数绑定时间的方法。泪崩...

...有哪位牛人解答一下如何给函数绑定事件嘛...给新人多一些关爱好么

异步平移是什么意思?

第一个例子中
f1(callback){
setTimeout(function(){
//f1的代码
callback();
},1000);
}
这样的话,不也还是先执行f1的代码,然后执行callback()即f2()吗?只不过是延迟执行,这里哪里体现了异步执行呢

你好,单个耗时的任务可以推迟到后面进行,但是多个耗时的任务依旧还是同步的执行,那么假如有10个均耗时3秒的任务,使用js 异步去执行,实际上依旧需要等待30秒,这样的话,并没有什么帮助,请问有办法实现10个耗时任务的异步么,使3秒内完成这10个任务

网上 一种说法是 js的大部分操作 都是 异步操作.这种说法正确吗?

为什么回调函数 只能有一个回调函数,不能f1(f2, f3)吗?
为什么事件绑定,可以多个回调函数,没有看出有什么不同的?谢谢

您好,阮老师,对回调函数还是了解得很模糊,能否再专门的细讲下这个模块呢?非常非常感谢!

这里应该是:
function f1(callback) {
setTimeout(function () {
// f1的任务代码
console.log("f1");
}, 5000);
callback();
}
function f2(){
console.log("f2");
}
f1(f2);

@fish:

应该是写错了,callback()要写在setTimeout()方法的外面。

不得不说,回调函数异步编程的讲解问题很大。

阮老师,这样写会不会有问题:
function f1(callback){

    setTimeout(function () {

      // f1的任务代码,如果f1是异步任务这里这样写是有问题的吧??没等f1出结果,callback已经执行了??

      callback();

    }, 1000);

  }

我的理解:
1、setTimeout不设置异步,只是防止f1出错强行退出;
2、f1/f2不构成异步,f1/f3构成异步,f2只是在事件“f1成功执行完成后对应的调用函数,和摁鼠标后执行的函数一个性质”;
3、单线程模拟异步应该是划分时间片来回切换多个函数?(程序片断),不至于让一个程序片断独占CPU资源,真正的异步应该有硬件支持。

引用何栋的发言:

@fish:

应该是写错了,callback()要写在setTimeout()方法的外面。

callback()不能写在setTimeout()方法的外面呀。因为阮老师都说了f2依赖f1的执行结果

信任勿喷,看完还是有点蒙啊,这里讲的四种方法,和程序的异步执行有啥关系?看起来是设计模式的东西,放在其他语言里也应该能实现。目前了解,setTimeout和Ajax这些自带函数会导致浏览器异步去执行代码。

我很想表达的一点是:异步编程的方法是有多种,但本质的原因是该事件的执行是“异步”的!比如网络下载,文件读取,延时调用等。这里讨论的方法只是代码上的表现方法而已!
大家不要误以为利用异步方式可以把任一种想要的效果变成异步的!

所以本文讨论的其实是异步的表现手法,使用promise可以方便的利用链式调用来达到代码“类同步”的效果,使用发布订阅可以把异步的过程拆分在代码的不同位置进行灵活控制。

不使用这些方法,你使用原本的那些异步事件的回调也不影响你功能的实现。只是新的方法可能更“方便理解”一些(是不是更方便取决于对异步对回调的认识)

引用小涛的发言:

异步的实现就是用定时器函数改变代码的执行顺序吗?

对呀,还是 执行 f1 然后再 f2 啊,疑问!?

引用whws的发言:

我只想知道 jQuery 是怎么给函数绑定事件的。。。。。

同问,不知道jquery如何给js函数绑定事件。。。

@Evan:

f1的任务和f2的任务本身是有依赖的,所以他们必须同步。而他们之中的f1又十分耗时,所以他们一起作为一个异步任务。



引用sakura的发言:

@Evan:

f1的任务和f2的任务本身是有依赖的,所以他们必须同步。而他们之中的f1又十分耗时,所以他们一起作为一个异步任务。

澄清了我疑问,谢谢,实际异步是靠setTimeout实现的,对么?

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接