阅读更多

7顶
1踩

编程语言

转载新闻 JavaScript 异步编程的 4 种方法

2012-12-25 17:33 by 副主编 WnouM 评论(14) 有8373人浏览
本文转载自阮一峰的博客

你可能知道,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
  • 大小: 14 KB
  • 大小: 23.3 KB
  • 大小: 23.3 KB
7
1
评论 共 14 条 请登录后发表评论
14 楼 hobotree 2013-02-20 12:11
allloveend 写道
setTimeout就是js的异步执行吗!!!!!???? 那我岂不是无师自通了……

我一开始看也这样想,其实这里只是用来表示一种耗时的javascript代码段。
关键不是在于f1().then(f2),这里f2还是在f1之后执行(可以说是同步执行),
关键在于如果你有一个方法f3耗时长的离谱,你希望f2还是能在f1之后执行,不能因为f3这粒老鼠屎而把浏览器锁死了,你这样写:
f1().then(f2);
f3();
那么此时f2作为f1的回调函数,会和f3异步执行,不受f3这粒老鼠屎的英雄。
顺便说一下,then()方法可链式执行,其执行也是异步的,即上面的代码写成
f1().then(f3).then(f2); f2照样不受f3影响
13 楼 kjj10 2012-12-27 09:33
9344187 写道
rainsilence 写道
原理差不多,只是加了层语法糖

什么是语法糖

原理上讲都是那几个设计模式,监听器或者观察者,语法糖就是说通过封装让你在使用的时候代码更简洁
12 楼 bruceeye 2012-12-26 10:36
11 楼 9344187 2012-12-26 10:10
rainsilence 写道
原理差不多,只是加了层语法糖

什么是语法糖
10 楼 wingsrao 2012-12-26 10:04
9 楼 allloveend 2012-12-26 10:00
setTimeout就是js的异步执行吗!!!!!???? 那我岂不是无师自通了……
8 楼 1927105 2012-12-26 09:16
看完还是将的设计模式,最终一步还是抽象。。
7 楼 tacyuuhon 2012-12-26 01:08
一楼亮了。
6 楼 kanme818 2012-12-25 22:28
马克,不错学习
5 楼 QiuQiu0034 2012-12-25 22:25
QiuQiu0034 写道
这种所谓的异步仍然是同步,javascript的延迟执行和定时执行只不过是编译器在适当地时间将任务加入到单线程的队列当中去了,这在javascript权威指南里解释的很清楚

这篇文章说白了就是讲了几种设计模式而已

确切的说是解释器,而不是编译器
4 楼 QiuQiu0034 2012-12-25 22:25
这种所谓的异步仍然是同步,javascript的延迟执行和定时执行只不过是编译器在适当地时间将任务加入到单线程的队列当中去了,这在javascript权威指南里解释的很清楚

这篇文章说白了就是讲了几种设计模式而已
3 楼 yuwenchun 2012-12-25 21:43
2 楼 rainsilence 2012-12-25 21:05
原理差不多,只是加了层语法糖
1 楼 Lamp兄弟 2012-12-25 19:22

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • PHP实现执行外部程序的方法详解

    本文实例讲述了PHP实现执行外部程序的方法。分享给大家供大家参考,具体如下: 在一些特殊情况下,会使用PHP调用外部程序执行,比如:调用shell命令、shell脚本、可执行程序等等,今天在源码中了解了一下PHP执行外部程序的方法,借此机会顺便整理一下。 在源码中 exec.h文件中,列出了可调用外部程序的几个函数,蓝色框里的两个函数只是辅助作用,本文只对其他几个函数做介绍。 前提 1 PHP没有运行在安全模式,关掉安全模式,即:safe_mode = off 2 禁用函数列表 disable_functions =  exec, system, shell_exec,proc_open,

  • 解决Windows下PHP的exec、shell_exec等函数不能正常运行的方法

    在Windows平台下安装PHP后,在使用PHP的exec、shell_exec等函数时,经常会遇到报错:unable to fork...,这实际上是因为PHP对cmd.exe没有足够的权限引起的。 解决方法:为C:\Windows\System32\cmd.exe(系统不是安装在C盘请相应更改)添加IUSR_ComputerName (ComputerName是你的计算机名称)用户的读取、运

  • 用批处理关闭php服务,php在执行bat脚本时,一直阻塞,如何解决

    我在项目中需要运行一个websocket服务(nodejs的代码),为了防止服务器重启之后此服务未启动,所以在中间件中对此服务进行了监控,如果服务未开启,则通过bat文件开启此服务。但现在遇到一个问题,就是exec()执行bat文件时会一直在执行,导致页面连接超时~中间件代码:public function handle($request, Closure $next){//功能:监控larave...

  • php执行exec外部程序失败,php在服务器执行exec命令失败的解决方法

    php在服务器执行exec命令失败的解决方法前言:本文针对windows php环境,linux系统不在讨论范畴。出于安全的原因,服务器是不允许php或者其他语言执行exec命令的,当你有特殊需要php在服务器执行exec命令时,你需要设置两个地方,不然就无法执行成功。1、修改php.ini找到php配置文件php.ini,如果是用星外php自动安装程序配置php环境,这个文件就在C:\WINDO...

  • php exec超时,php接口超时如何处理

    一般我们访问HTTP方式很多,主要是:curl, socket, file_get_contents() 等方法。如果碰到对方服务器一直没有响应的时候,我们就悲剧了,很容易把整个服务器搞死,所以在访问http的时候也需要考虑超时的问题。[ CURL 访问HTTP]CURL 是我们常用的一种比较靠谱的访问HTTP协议接口的lib库,性能高,还有一些并发支持的功能等。(推荐学习:PHP编程从入门到精通...

  • nginx php访问阻塞,php-cgi和php-fpm,Windows环境下解决Nginx+php并发访问阻塞问题。

    php-cgi是运行php,php-fpm是守护php-cgi进程nginx配置目录运行phplocation~\.php${root/var/www/html;fastcgi_indexindex.php;fastcgi_pass127.0.0.1:9000;fastcgi_paramSCRIPT_FILENAME$document_root$fastcgi_script_na...

  • php利用exec函数调用命令行实现简单的非阻塞

    - 全部PHP代码: /* * 利用exec实现非阻塞请求,提高请求20%的容量 * 1. php.ini需要去除disable_functions=exec来开启可使用exec函数 * 2. 利用了“命令行+api+参数”的请求过程,最终返回api的结果 * 3. 注意大多数命令行win与linux的不同,混用可能会报错 * * exec_non_blocking($api, ...

  • PHP中控制proc_open的执行时间

    目前想到有两个办法。 一个是fork方式。 发现直接通过进程的alarm信号方式无法控制proc_open的执行。所以用fork方式,通过fork让子进程执行proc_open,父进程则通过alarm的方式控制时间。但是这么做会造成如果有输出值的话会很麻烦,要让子进程一直执行下去,因为没找到好的办法让父进程能获取子进程的输出。当然通过进程间通信能解决,但是太麻烦了。。。 另外个str

  • PHP timeout情况_php脚本运行时的超时机制详解

    在做php开发的时候,经常会设置max_input_time、max_execution_time,用来控制脚本的超时时间。但却从来没有思考过背后的原理。趁着这两天有空,研究一下这个问题。超时配置php的ini配置如何起作用,这是一个老生常谈的话题了。首先,我们在php.ini里进行配置。当php启动的时候(php_module_startup阶段),会尝试读取ini文件并解析。解析过程简单来说,...

  • php idletimeout,php调试超时的问题 百度谷歌了N次 求解决 谢谢

    用Phpstorm xdebug调试的时候 总是十几分这样就超时了。环境window phpstudy php5.3 apache mysql显示500错误做了一下测试1.在php文件 set_time_limit(0)2.php.ini 设置xdebug xdebug.remote_cookie_expire_time = 36003.在httpd.conf加了行 Timeout 3...

  • php+方法返回多个参数,PHP中调用外部程序,及其参数与返回值

    用了一下午,终于弄明白了如何在PHP代码中调用外部程序。在PHP中调用外部程序主要有两个函数,system和exec。system的原型为string system(string command [, int $return_var])。system本身具有打印命令执行输出的功能,也就是说,程序中的输出printf()PHP页面中显示。如果程序成功执行,则system的返回值为程序输出的最后一行,...

  • php 批处理 超时,Windows批处理:模拟“超时”命令

    此代码等待60秒,然后检查%ProgramName%是否正在运行。为了增加这个时间,改变WaitForMinutes的值。要减少检查之间的时间间隔,请将WaitForSeconds设置为您希望等待的秒数。@echo off set ProgramName=calc.exe set EndInHours=2 :: How Many Minutes in between each check to s...

  • Windows 下使用 PhpStorm + Xdebug 超时

    Windows 下使用 PhpStorm + Xdebug 超时

  • PHP调用外部程序的方法

    在PHP中调用外部命令,可以用如下三种方法来实现:   方法一:用PHP提供的专门函数(四个): PHP提供4个专门的执行外部命令的函数:exec(), system(), passthru(), shell_exec() 1)exec() 原型: string exec ( string $command [, array &$output [,

  • PHP调用Linux系统的常用函数

    PHP调用Linux系统的常用函数   1、exec函数   php   $test = "ls /tmp/test"; //ls是linux下的查目录,文件的命令   exec($test,$array); //执行命令   print_r($array);   ?>   2、system函数      $test = "ls /tmp/te

  • php执行shell不阻塞方法

    大家都知道php执行系统命令的方法有: system() 输出并返回最后一行shell结果。 exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面。 passthru() 只调用命令,把命令的运行结果原样地直接输出到标准输出设备上。这些方法都会等待命令执行完毕后才会继续执行后续的程序 不阻塞可以将命令输出的内容写入系统的一个回收站文件,这样程序就不会阻塞

  • php 不阻塞执行的方式总结,这些是基础的

    php 用linux 后台不阻塞执行 脚本文件 nohup cp 大文件 /目录 shell_exec("nohup php /vagrant/html/cli.php /order{$v['id']} {$k} > /dev/null 2>&1 &"); php fastcgi_finish_request 此函数冲刷(flush)所有响...

Global site tag (gtag.js) - Google Analytics