异步编程的前世今生
1、为什么需要异步编程
异步编程是相对同步编程来说的,开发项目时,开发者总是希望,程序的执行顺利能按照编程的顺序从上至下执行,这样符合人的思维易于理解,但是现实开发中,一般都事与愿违,相信每个开发者或多或少遇到过,程序执行到某些复杂的、耗时的任务时,往往要开启异步任务去执行,这样就不会阻塞当前任务,让当前任务继续执行,当异步任务执行完后,再将异步任务执行完的结果传给当前任务使用。所以异步编程主要为提高程序整体的执行效率。
2、异步编程模型
以JavaScript语言为例:
2.1、最传统的异步编程模型:异步 + 回调
function sync(callback) {
你自己的代码;
async(function() {
var result = 你自己的代码;
callback(result);
});
}
如下是node.js API的示例:
var agent = require('superagent');
agent.get('http://www.bing.com')
.end(function(err, res) {
if(err) {
console.log(err);
} else {
console.log('http status: ' + res.status);
console.log(res.header);
}
});
简单这样一层异步回调,代码可读性还好,当遇到多层嵌套回调的场景时,简直可以说是反人类的东西,可读性变得很差,看起来也很难受。
例如下面这段回调嵌套代码:
var fs = require('fs');
fs.readFile('./a.txt', function(err1, data1) {
fs.readFile('./b.txt', function(err2, data2) {
fs.writeFile('./ab.txt', data1 + data2, function(err) {
console.log('read and write done!');
});
});
});
看完之后整个人都不好了。
为了缓解这种问题,Promise出现了。
2.2、更友好的异步编程模型:Promise
Promise风格异步函数的基本写法:
如果用setTimeout来模拟要进行的异步操作,以下是让异步函数返回promise的基本写法。调用Promise构造函数,生成一个promise对象,然后return它。把你的代码包裹在匿名函数function(resolve, reject){ … } 里面,作为参数传给Promise构造函数。resolve和reject是promise机制内部已经定义好的函数,传给你用来改变promise对象的状态。在你的异步代码结束的时候调用resolve来表示异步操作成功,并且把结果传给resolve作为参数,这样它可以传给下一个异步操作。
function asyncFunc() {
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('asyncFn1 is done');
resolve('asyncFn1 value');
}, 1000);
});
return promise;
}
例如有三个异步函数:
function asyncFunc1() {
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('asyncFunc1 is done');
resolve('asyncFunc1 value');
}, 1000);
});
return promise;
}
function asyncFunc2(arg) {
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('asyncFunc2 is done');
resolve(arg + ' asyncFunc2 value');
}, 1000);
});
return promise;
}
function asyncFunc3(arg) {
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('asyncFunc3 is done');
resolve(arg + ' asyncFunc3 value');
}, 1000);
});
return promise;
}
通过Promise.then()串联异步函数,使得异步函数一个接着一个的顺序执行。
asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3)
.then(function(arg) {
console.log(arg);
});
用Promise组织异步函数,完全能取代回调嵌套,关键是代码可读性变得友好。
2.3、异步编程终极模型:async/await
async函数返回的是一个 Promise 对象,然后通过await等待返回值,如果返回值是Promise对象,就会阻塞后面代码的执行,直到等着Promise对象resolve,然后得到resolve的值,才停止阻塞继续执行后面的代码。
前面的例子,如果用asyn/await来实现,如下:
async function doSomething() {
const temp1 = await asyncFunc1();
const temp2 = await asyncFunc2(temp1);
const result = await asyncFunc3(temp2);
console.log(`result is ${result}`);
}
简直太简洁,代码可读性非常好,关键还非常符合人的思维。
其实关于async/await这种异步编程模型,在一些其他编程语言叫做:协程。
比如:C#,Python等都有async/await的异步编程,此方式最大的优点就是以编写同步代码的方式实现异步编程。
最后还想提一下,Kotlin语言推出的协程,涵盖几乎所有编程语言(C#,Python,Go等语言原生支持协程)协程的实现方式,可以预见未来Java、Android开发的异步编程很美好。