我所了解的JavaScript异步编程

Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)

我们知道 javascript语言是单线程机制。所谓单线程就是按次序执行,执行完一个任务再执行下一个。对于浏览器来说,也就是无法在渲染页面的同时执行代码
  单线程机制的优点在于实现起来较为简单,运行环境相对简单。缺点在于,如果中间有任务需要响应时间过长,经常会导致页面加载错误或者浏览器无响应的状况。这就是所谓的“同步模式”,程序执行顺序与任务排列顺序一致。
  对于浏览器来说,同步模式效率较低,耗时长的任务都应该使用异步模式;"异步模式"非常重要,在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。而在服务器端,异步模式则是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。。

异步模式的四种方式:

1.回调函数callback

所谓回调函数,就是将函数作为参数传到需要回调的函数内部再执行。

  • 典型的例子就是发送ajax请求。例如:

 

$.ajax({
    async: false,cache: false,dataType: 'json',url: "url",success: function(data) {
      console.log('success');
    },error: function(data) {
     console.log('error');
    }
  })

/*当发送ajax请求后,等待回应的过程不会堵塞程序运行,耗时的操作相当于延后执行。
回调函数的优点在于简单,容易理解,但是可读性较差,耦合度较高,不易于维护。*/
  • 假定有两个函数f1和f2,后者等待前者的执行结果。

 

f1();
f2();

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

 

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

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

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

2.事件驱动(Event-Driven)

事件驱动,指的是由鼠标和热键的动作引发的一连串的程序操作。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
例如,为页面上的某个绑定click事件;

 

$('#Btn').onclick(function(){
   console.log('click button');
});

绑定事件相当于在元素上进行监听,是否执行注册的事件代码取决于事件是否发生。
  优点在于容易理解,一个元素上可以绑定多个事件,有利于实现模块化;但是缺点在于称为事件驱动的模型后,流程不清晰。

3.发布/订阅

发布订阅模式(publish-subscribe pattern)又称为观察者模式(Observer pattern)。该模式中,有两类对象:观察者和目标对象。目标对象中存在着一份观察者的列表,当目标对象的状态发生改变时,主动通知观察者,从而建立一种发布/订阅的关系。
  上一节的"事件驱动",完全可以理解成"信号"。我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(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);

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

4.promise模式

这才是重点,Promises对象是Commonjs工作组提出的一种规范,目的是为异步编程提供统一接口简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。使用Promise对象可以用同步操作的流程写法来表达异步操作,避免了层层嵌套的异步回调,代码也更加清晰易懂,方便维护,也可以捕捉异常。
  promise模式在任何时刻都处于以下三种状态之一:未完成(unfulfilled)、已完成(resolved)和拒绝(rejected)。以Commonjs Promise/A 标准为例,promise对象上的then方法负责添加针对已完成和拒绝状态下的处理函数。then方法会返回另一个promise对象,以便于形成promise管道,这种返回promise对象的方式能够支持开发人员把异步操作串联起来,如then(resolvedHandler,rejectedHandler); 。resolvedHandler 回调函数在promise对象进入完成状态时会触发,并传递结果;rejectedHandler函数会在拒绝状态下调用
  Jquery在1.5的版本中引入了一个新的概念叫Deferred,就是Commonjs promise A标准的一种衍生。可以在jQuery中创建
$.Deferref的对象。同时也对发送ajax请求以及数据类型有了新的修改,参考JQuery API。
  简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如,f1的回调函数f2,可以写成:

 

f1().then(f2);

f1要进行如下改写:

 

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

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

比如,指定多个回调函数

 

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

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

 

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

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

  • 在ES 6中原生提供了Promise对象,Promise对象代表了某个未来才会知道结果的事件(一般是一个异步操作),并且这个事件对外提供了统一的API,可供进一步处理。

Promise 对象有以下两个特点:
1)、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。

2)、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

  有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

  Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

一个简单的demo:

 

function fn(num) {
  var promise = new Promise(function(resolve,reject) {
     if (/* 异步操作成功 */){
       resolve(value);
     } else {
       reject(error);
   }
});

promise.then(function(value) {
 // success
},function(value) {
 // failure
});

  Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法

  如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从「未完成」变为「成功」(即从 pending 变为 resolved);

  如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从「未完成」变为「失败」(即从 pending 变为 rejected)。

promise 基本的 api

1.Promise.resolve()
2.Promise.reject()
3.Promise.prototype.then()
4.Promise.prototype.catch()
5.Promise.all() // 所有的完成

 

 var p = Promise.all([p1,p2,p3]);

6.Promise.race() // 竞速,完成一个即可

参考:
http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/then


 

相关文章

前言 做过web项目开发的人对layer弹层组件肯定不陌生,作为l...
前言 前端表单校验是过滤无效数据、假数据、有毒数据的第一步...
前言 图片上传是web项目常见的需求,我基于之前的博客的代码...
前言 导出Excel文件这个功能,通常都是在后端实现返回前端一...
前言 众所周知,js是单线程的,从上往下,从左往右依次执行,...
前言 项目开发中,我们可能会碰到这样的需求:select标签,禁...