基于Javascript 的异步编程分析

2015-07-13 12:25陈浩
电脑知识与技术 2015年13期

陈浩

摘要:异步编程带来的问题在客户端Javascript中并不明显,但随着服务器端Javascript越来越广的被使用,大量的异步IO操作使得该问题变得明显。许多不同的方法都可以解决这个问题,本文针对此问题讨论了一些方法。

关键词:异步编程;Javascript;异步IO

中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2015)13-0080-02

Abstract: Asynchronous programming problems caused by the client Javascript is not obvious, but with the server-side Javascript is used more widely, a large number of asynchronous IO operation so that the problem becomes apparent. Many different methods can solve this problem, this paper discusses some of the ways this problem.

Key words: Asynchronous Programming; Javascript; Asynchronous IO

1 JavaScript 异步编程简述

异步指的是函数的调用并不直接返回执行的结果,而往往是通过回调函数异步的执行。回调函数,其实就是调用用户提供的函数,该函数往往是以参数的形式提供的。回调函数并不一定是异步执行的。

varfn = function(callback) {

// do something here

callback.apply(this, para);

};

varmycallback = function(parameter) {

// do someting in customer callback

};

// call the fn with callback as parameter

fn(mycallback);

上述的例子中,回调函数是被同步执行的。大部分语言都支持回调,C++可用通过函数指针或者回调对象,Java一般也是使用回调对象。

在Javascript中有很多通过回调函数来执行的异步调用,例如setTimeout()或者setInterval()

setTimeout(function(){

console.log("this will be exectued after 1 second!");

},1000);

上例中,setTimeout直接返回,匿名函数会在1000毫秒后异步触发并执行,完成打印控制台的操作。也就是说在异步操作的情境下,函数直接返回,把控制权交给回调函数,回调函数会在以后的某一个时间片被调度执行。之所以要实现异步,则需要熟悉Javascript的线程模型。

2 Javascript线程模型和事件驱动

Javascript是单线程的,为了能实现异步执行,就需要明白Javascript在浏览器中的事件驱动(event driven)机制。事件驱动一般通过事件循环(event loop)和事件队列(event queue)来实现的。假定浏览器中有一个专门用于事件调度的实例,该实例可以是一个线程,我们可以称之为事件分发线程event dispatch thread,该实例的工作就是一个不结束的循环,从事件队列中取出事件,处理所有很事件关联的回调函数(event handler)。注意回调函数是在Javascript的主线程中运行的,而非事件分发线程中,以保证事件处理不会发生阻塞。

通过事件驱动机制,可以想象Javascript的编程模型就是响应一系列的事件,执行对应的回调函数。很多UI框架都采用这样的模型。异步的主要目的是处理非阻塞,在和HTML交互的过程中,会需要一些IO操作,如果这些操作是同步的,就会阻塞其它操作,用户的体验就是页面失去了响应。

由此可见Javascript通过事件驱动机制,在单线程模型下,以异步回调函数的形式来实现非阻塞的IO操作。

3 Javascript异步编程的缺陷

Javascript的单线程模型有很多好处,但同时也带来了很多问题。

3.1 代码可读性

如果某个操作需要经过多个非阻塞的IO操作,每一个结果都是通过回调如下所示:

operation1(function(err, result) {

operation2(function(err, result) {

operation3(function(err, result) {

operation4(function(err, result) {

operation5(function(err, result) {

// do something useful

})

})

})

})

})

意大利面条式的代码很难维护。这样的情况更多的会发生在server side的情况下。

3.2 流程控制

异步带来的另一个问题是流程控制,例如要访问三个网站的内容,当三个网站的内容都得到后,合并处理,然后发给后台。具体代码如下:

varurls = ['url1','url2','url3'];

var result = [];

for (var i = 0, len = urls.length(); i

$.ajax({

url: urls[i],

context: document.body,

success: function(){

//do something on success

result.push("one of the request done successfully");

if (result.length === urls.length()) {

//do something when all the request is completed successfully

}

}});

}

通过检查result的长度的方式来决定是否所有的请求都处理完成,这种方式不可靠。

3.3 异常和错误处理

在异步的方式下,异常处理分布在不同的回调函数中,无法在调用的时候通过try…catch的方式来处理异常, 所以很难做到有效清楚。

4 改进式Javascript异步编程方案

为了解决Javascript异步编程带来的问题,提出了以下改进式解决方案。

Promise对象曾经以多种形式存在于很多语言中。Promise的核心是它的then方法,我们可以使用这个方法从异步操作中得到返回值,或者是异常。then有两个可选参数,分别处理成功和失败的情景。

var promise = doSomethingAync()

promise.then(onFulfilled, onRejected)

异步调用doSomethingAync返回一个Promise对象promise,调用promise的then方法来处理成功和失败。和以前的区别在于,首先异步操作有了返回值,虽然该值只是一个对未来的承诺;其次通过使用then,程序员可以有效的控制流程异常处理,决定如何使用这个来自未来的值。

Promise提供更便捷的流程控制,例如Promise.all()可以解决需要并发的执行若干个异步操作,等所有操作完成后进行处理。

var p1 = async1();

var p2 = async2();

var p3 = async3();

Promise.all([p1,p2,p3]).then(function(){

// do something when all three asychronized operation finished

});

对于异常处理,

doA()

.then(doB)

.then(null,function(error){

// error handling here

})

如果doA失败,它的Promise会被拒绝,处理链上的下一个onRejected会被调用,在这个例子中就是匿名函数function(error){}。比起原始的回调方式,不需要在每一步都对异常进行处理。

5 总结

随着ES6的定义,Javascript的语法变得越来越丰富,更多的功能带来了很多便利,原本简洁,单一目的的Javascript变得复杂,要承担更多的任务,本文讨论了一些方法,具体需要根据情况选择一个适合的方法。

参考文献:

[1] 李世胜.基于预测的JavaScript类型系统研究[J].计算机研究与发展,2012(2).

[2] 杨俊.JavaScript面向对象编程探析[J].办公自动化,2010(8).

[3] 钱宇虹.如何用Java回调和线程实现异步调用[J].软件工程师,2013(10).

[4] 吴通.基于动态分析的JavaScript代码推荐[J].计算机工程,2014(10).