JavaScript环境中产生异步操作的函数分为两大类:计时函数
和I/O函数
。如果要在应用中定义复杂的异步操作,就要使用者两类异步函数作为基本的构造快。本文没有对某个知识点细致展开,仅供思路参考。
1. 计时函数
先看一个经典的例子:
for(var i = 0;i < 5; i++){
setTimeout(function(){
console.log(i);
},5000);
}
console.log(i);
结果输出什么?立马输出一个5,5秒钟过后连续输出5个5(呜呜呜呜呜~),搞懂为什么这样输出5,需要知道3件事:
- 这里只有一个
i
变量,作用域由var
定义,不管i怎么变,都指向同一个内存区域; - 循环结束后 i=== 5;
- JavaScript事件处理器在线程空闲之前不会执行。
来,再来和我背一遍口诀:先同步后异步最后回调
。在本例子中:
- 同步事件:for循环(不包含内部的setTimeout);外部的console.log(i);
- 异步事件:for循环内部的setTimeout。
先执行for循环,遇到setTimeout压入延迟事件队列,一边循环一边压入队列;for循环结束执行外部的console.log(i),此时i=5,故立即输出5,此时同步事件执行完毕,接下来开始执行异步事件setTimeout,5个setTimeout事件等待5秒同时在等待,所以5秒结束连续输出5个5。
再看一个例子:
var start = new Date;
setTimeout(function(){
var end = new Date;
console.log('time using:' + (end - start) + 'ms');
},5000);
while(new Date - start < 1000){};
猜猜结果是什么?
调换一下时间:
var start = new Date;
setTimeout(function(){
var end = new Date;
console.log('time using:' + (end - start) + 'ms');
},2000);
while(new Date - start < 5000){};
这里唯一想说的是:像setTimeout
或是setInterval
并非是精确计时的, setTimeout与setInterval在不同浏览器下的差异。
2. I/O函数
这里的I/O
是一个广义的概念,包括读写文件,GET或POST请求,异步读取值值函数等等。一个常见的读文件的操作:fs.js
var fs = require('fs');
fs.readFile('data.txt',function(err,data){
if(err){
return console.log(err);
}
console.log(data.toString())
});
data.txt的内容:
hello
world
hello
async
node fs.js:
没毛病!设想一个场景,在另外一个函数,假设名字叫panfen()
,里面有一堆代码,其中需要用到从data.txt文件读取的数据,常见的做法是把fs.readFile操作写在panfen()
里面,处理data的操作都写在fs.readFile的callback里面,如果callback里面还要写数据呢?继续执行fs.writeFile在它的callback里面执行其他操作...
var fs = require('fs');
fs.readFile('data.txt',function(err,data){
if(err){
return console.log('failed to read data!');
}else{
fs.writeFile('./data_copy.txt', data, function(){
if(err){
...
}else{
...
}
});
}
});
这就是回调金字塔
。其弊端是操作强耦合、维护代价高。如何做到理想的异步呢?
灵机一动这样写:
var fs = require('fs');
var data = fs.readFile('data.txt',function(err,data){
return data ? data : err;
});
fs.writeFile('./data_copy.txt', data, function(){
...
});
然而,根据先同步后异步最后回调
的法则,fs.writeFile里面使用到的data,肯定是undefined
3.Promise
var fs = require('fs')
function myReadFile(filepath) {
return new Promise(function (resolve, reject) {
fs.readFile(filepath, function (err, data) {
data ? resolve(data) : reject(err);
});
});
}
function myWriteFile(filepath,data) {
return new Promise(function (resolve, reject) {
fs.writeFile(filepath,data,function (err) {
err ? reject(err) : resolve();
});
});
}
function test() {
myReadFile('data.txt').then(function(data){
myWriteFile('./data1.txt',data)
});
}
test();
Promise的特点在于可以用then方法向下传递值。
4. Generator
4.1 function*
关于生成器函数,这里不作具体介绍,简单看一例子:
function* GenFunc(){
yield [1,2];
yield* [3,4];
yield "56";
yield* "78"
}
var gen = GenFunc();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
yield
和yield*
的区别:
-
yield
只返回右值; -
yield*
将函数委托给另一个生成器,或可迭代的对象(字符串、数组、arguments、Map、Set)
注:
yield
关键字只能出现在生成器函数里面!!!否则会报错:Unexpected strict mode reserved word
4.2 co
co是基于Generator的一个库:
var fs = require('fs');
var co = require('co');
function myReadFile(filepath){
return function(cb){
fs.readFile(filepath,cb);
};
}
function myWriteFile(filepath,data){
return function(cb){
fs.writeFile(filepath,data,cb);
};
}
co(function* GenFunc(){
var data = yield myReadFile('data.txt');
yield myWriteFile('data1.txt',data);
}).catch(function(err){
console.log(err);
});
看起来是不是神清气爽?
4.3 Koa
Koa是基于Generator和co的web框架。
var koa = require('koa');
var app = koa();
app.use(function* (next){
console.log(1);
yield next;
console.log(3);
});
app.use(function* (){
console.log(2);
this.body = 'hello Koa!';
});
app.listen(8080);
启动程序,流浪器输入localhost:8080
,看到页面有hello Koa!
,控制台输入1
2
3
5.async/awit
随着 Node 7 的发布,越来越多的人开始研究async/await,据说这是异步编程终级解决方案的 。个人觉得没有最好,只有更好。用这种方式改写:
'use strict'
var fs = require('fs')
function myReadFile(filepath) {
return new Promise(function (resolve, reject) {
fs.readFile(filepath, function (err, data) {
data ? resolve(data) : reject(err);
});
});
}
function myWriteFile(filepath,data) {
return new Promise(function (resolve, reject) {
fs.writeFile(filepath,data,function (err) {
err ? reject(err) : resolve();
});
});
}
async function test() {
const data = await myReadFile('data.txt');
await myWriteFile('./data1.txt',data);
}
test();
当然,这个例子还不足以体现async/awit的优势。
6. 总结
JavaScript的异步操作可能是区别其他语言比较大的一点,也是一个难点,不过也是很有趣的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。