【JS】异步处理机制的几种方式

时间: 2018-07-17阅读: 842标签: 异步

JavaScript语言的执行环境是"单线程"(single thread,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推)。

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

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

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

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


一、回调函数(callback)

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

f1();
f2();


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

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


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

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

注意 区分 回调函数和异步


回调并不一定就是异步。他们自己并没有直接关系。

简单区分 同步回调 和 异步回调

同步回调 实例代码:

function A(callback){
    console.log("I am A");
    callback();  //调用该函数
}
function B(){
   console.log("I am B");
}
A(B);


异步回调:因为js是单线程的,但是有很多情况的执行步骤(ajax请求远程数据,IO等)是非常耗时的,如果一直单线程的堵塞下去会导致程序的等待时间过长页面失去响应,影响用户体验了。

如何去解决这个问题呢,我们可以这么想。耗时的我们都扔给异步去做,做好了再通知下我们做完了,我们拿到数据继续往下走。

var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);   //第三个参数决定是否采用异步的方式
xhr.send(data);
xhr.onreadystatechange = function(){
    if(xhr.readystate === 4 && xhr.status === 200){
       ///do something
    }
}


上面是一个代码,浏览器在发起一个ajax请求,会单开一个线程去发起http请求,这样的话就能把这个耗时的过程单独去自己跑了,在这个线程的请求过程中,readystate 的值会有个变化的过程,每一次变化就触发一次 onreadystatechange 函数,进行判断是否正确拿到返回结果。

之前在学习 nodejs时也会遇到这样的问题

var fs=require('fs');
//console.log('1');
//fs.readFile('mime.json',function(err,data){
//    //console.log(data);
//    console.log('2');
//})
//console.log('3');
function getMime(){
    //1
    fs.readFile('mime.json',function(err,data){
        //console.log(data.toString());
        return data;//3
    })
    //2
    //return '123';
}
console.log(getMime());  /*由于异步操作没有拿到数据,如何解决,通过异步操作*/


解决的 办法 是

var fs=require('fs');
function getMime(callback){
    fs.readFile('mime.json',function(err,data){
        callback(data);
    })
}
getMime(function(result){
    console.log(result.toString());
})


二、事件监听

采用事件驱动模式。

任务的执行不取决代码的顺序,而取决于某一个事件是否发生。

监听函数有:on,bind,listen,addEventListener,observe

还是以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.

这种方法的优点:比较容易理解,可以绑定多个事件,每一个事件可以指定多个回调函数,而且可以去耦合,有利于实现模块化。

这种方法的缺点:整个程序都要变成事件驱动型,运行流程会变得不清晰。

事件监听方法:


(1)onclick方法

element.onclick=function(){
   //处理函数
}

优点:写法兼容到主流浏览器

缺点:当同一个element元素绑定多个事件时,只有最后一个事件会被添加

例如:

element.onclick=handler1;
element.onclick=handler2;
element.onclick=handler3;

上诉只有handler3会被添加执行,所以我们使用另外一种方法添加事件


(2)attachEvent和addEvenListener方法

//IE:attachEvent
elment.attachEvent("onclick",handler1);
elment.attachEvent("onclick",handler2);
elment.attachEvent("onclick",handler3);

上述三个方法执行顺序:3-2-1;

//标准addEventListener
elment.addEvenListener("click",handler1,false);
elment.addEvenListener("click",handler2,false);
elment.addEvenListener("click",handler3,false);

执行顺序:1-2-3;

PS:该方法的第三个参数是泡沫获取,是一个布尔值:当为false时表示由里向外,true表示由外向里。

<div id="id1">
    <div id="id2"></div>
</div>


document.getElementById("id1").addEventListener("click",function(){console.log('id1');},false);
document.getElementById("id2").addEventListener("click",function(){console.log('id2');},false);
//点击id=id2的div,先在sonsole中输出,先输出id2,在输出id1

document.getElementById("id1").addEventListener("click",function(){console.log('id1');},false);
document.getElementById("id2").addEventListener("click",function(){console.log('id2');},true);
//点击id=id2的div,先在sonsole中国输出,先输出id1,在输出id2


三、发布/订阅

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

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

 

四、promise对象(promise 模式)

(1)promise对象是commonJS工作组提出的一种规范,一种模式,目的是为了异步编程提供统一接口。

(2)promise是一种模式,promise可以帮忙管理异步方式返回的代码。他讲代码进行封装并添加一个类似于事件处理的管理层。我们可以使用promise来注册代码,这些代码会在在promise成功或者失败后运行。

(3)promise完成之后,对应的代码也会执行。我们可以注册任意数量的函数再成功或者失败后运行,也可以在任何时候注册事件处理程序。

(4)promise有两种状态:1、等待(pending);2、完成(settled)。

promise会一直处于等待状态,直到它所包装的异步调用返回/超时/结束。

(5)这时候promise状态变成完成。完成状态分成两类:1、解决(resolved);2、拒绝(rejected)。

(6)promise解决(resolved):意味着顺利结束。promise拒绝(rejected)意味着没有顺利结束。

//promise
var p=new Promise(function(resolved))
//在这里进行处理。也许可以使用ajax
setTimeout(function(){
   var result=10*5;
   if(result===50){
      resolve(50);
   }else{
     reject(new Error('Bad Math'));
  }
},1000);
});
p.then(function(result){
    console.log('Resolve with a values of %d',result);
});
p.catch(function(){
   console.error('Something went wrong');
});

(1)代码的 关键在于setTimeout()的调用。

(2)重要的是,他调用了函数resolve()和reject()。resolve()函数告诉promise用户promise已解决;reject()函数告诉promise用户promise未能顺利完成。

(3)另外还有一些使用了promise代码。注意then和catch用法,可以将他们想象成onsucess和onfailure事件的处理程序。

(4)巧妙地方是,我们将promise处理与状态分离。也就是说,我们可以调用p.then(或者p.catch)多少次都可以,不管promise是什么状态。

(5)promise是ECMAscript 6管理异步代码的标准方式,javascript库使用promise管理ajax,动画,和其他典型的异步交互。

简单的说,它的思想是:每一个异步任务返回一个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);

而且,它有一个前面三种方法都没有的好处:如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。

所以你不用担心错过某一个事件或者信号。

这种方法的缺点:编写和理解都相对比较难。

 

五、优雅的async/await

参考链接:

http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html

https://www.cnblogs.com/chengxs/p/6497575.html

https://segmentfault.com/a/1190000008489550

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/933

关闭

Js异步时序问题

不知你是否遇到过,向后台发送了多次异步请求,结果最后显示的数据却并不正确 -- 是旧的数据。具体情况:用户触发事件,发送了第 1 次请求

EventProxy的使用 - 解决异步回调地狱

最近在看node社区的nodeclub源码,看到一个玩意EventProxy,这里记录一下基本语法,EventProxy 可以理解为一个基于事件机制对复杂的业务逻辑进行解耦的工具,可以解决javascript异步回调地狱问题的工具

ES6 Promise的执行顺序和异步性

我们知道Promise用来处理异步操作,异步操作的执行顺序是相对比较复杂的,如果再涉及到链式调用,则情况会变得更复杂。这篇文章会通过一个相对简单的代码示例来让大家对Promise的执行顺序和异步性有正确的理解

Js如何处理循环的异步操作

compute.exec()这是个异步方法,在里面处理一些实际业务,这时候打印出来的很可能就是300,300,300(因为异步for循环还没有等异步操作返回Promise对象过来i值已经改变)

怎样取消 JavaScript 中的异步任务?

有时候执行异步任务可能是很困难的,尤其是在特定的编程语言不允许取消被错误启动或不再需要的操作时。幸运的是 JavaScript 提供了非常方便的功能来中止异步活动。在本文中,你可以学到如何创建可中止的函数。

ES6 Promise all 的实现

Promise 是ES6语法标准里的新定义,一般用来处理异步方法的同步执行。本文主要介绍Promise all的用法和实现。加入有以下场景:我有5个异步方法

js异步加载方式有哪些?_详解异步加载js的多种方案

js异步加载又被称为非阻塞加载,浏览器在下载JS的同时,还会进行后续页面处理。那么如何实现js异步加载呢?下面整理了多种实现方案供大家参考。异步加载js方案:Script Dom Element、onload时的异步加载、$(document).ready()、async属性、defer属性、es6模块type=module属性

Nodejs 处理异步(获取异步数据并处理)的方法

回调函数方式:将异步方法如readFile封装到一个自定义函数中,通过将异步方法得到的结果传给自定义方法的回调函数参数。事件驱动方式:使用node events模块,利用其EventEmitter对象

ajax异步图片/文件上传的实现_利用FormData对象

在前后分离开发中,经常会遇到上传文件的情况,后端会有一个文件上传接口,通过ajax上传成功后返回图片地址url给前端。

Js异步编程async/await 函数

提起异步编程,大家可能会想到事件监听、回调函数、发布订阅、Promise 对象、Generator 函数、async 函数等,本篇主要讲解的是 async 函数,很多人认为它是异步编程的终极解决方案。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!