ECMAScript6中的异步操作

Generator函数

Generator函数是ES6提供的一种异步编程解决方案,我们可以将Generator函数理解成一个状态机,执行这个函数会返回一个遍历器对象。Generator函数内部使用yield语句,没执行一次函数的next方法,执行到下一个yield,直到函数结束,从而达到异步调用函数的目的。此外,yield本身没有返回值,next方法可以带一个参数,该参数将被当做上一个yield语句的返回值,实现在generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//一般与for of结合的使用方法,不需要使用next方法
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a) {
var length = a.length;
for (var i = 0; i < length; i++) {
var item = a[i];
if (typeof item !== 'number') {
yield* flat(item); //在generator函数中调用另一个generator函数,要使用yield*
} else {
yield item;
}
}
};
for (var f of flat(arr)) {
console.log(f);
} //1,2,3,4,5,6

//通过next函数注入不同的值
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true } 5+24+13=42

//回调函数的改写
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();

loader.next();// 加载UI
loader.next(); //// 卸载UI

//部署Ajax操作
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response); //将response作为值传入
});
}
var it = main();
it.next(); //执行request操作

Promise对象

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。Promise独享的状态(pending、resolved、rejected)不受外界影响,只有异步操作的结果可以决定当前是哪一种状态。一旦状态发生改变,就不再变。如果某些事件不断反复发生,一般阿狸说,使用stream模式比部署Promise更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//基本用法
var promise = new Promise(function(resolve, reject) { //接收一个函数,函数有两个参数
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) { //接收两个函数,分别在成功和失败的时候执行
// success
}, function(value) {
// failure
});

//异步加载图片
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
var image = new Image();

image.onload = function() {
resolve(image);
};

image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};

image.src = url;
});
}

//generator和promise的结合
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
var g = function* () {
try {
var foo = yield getFoo();
console.log(foo);
} catch (e) {
console.log(e);
}
};
function run (generator) {
var it = generator();

function go(result) {
if (result.done) return result.value;

return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}

go(it.next());
}
run(g);

异步操作和Async函数

ES6之前,异步编程的方法,大概有:回调函数、事件监听、发布/订阅、Promise对象,ES6则应用generator函数是异步编程进入全新的阶段,ES7的Async函数更是提出了异步编程的终极解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//读取文件
//1.回调函数
fs.readFile('/etc/passwd', function (err, data) {
if (err) throw err;
console.log(data);
});

//2.Promise
fs.readFile(fileA, function (err, data) {
fs.readFile(fileB, function (err, data) {
// ...
});
}); //读入A后,读取B,则会出现多重嵌套

//3.协程
function *asnycJob() {
// ...其他代码
var f = yield readFile(fileA);
// ...其他代码
}

//4.generator
var fs = require('fs');

var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};

var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

//5.Async
var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab'); //await表示紧跟后面的表达式需要等待结果
var f2 = await readFile('/etc/shells'); //返回Promise对象,如果不是,则转为promise
console.log(f1.toString());
console.log(f2.toString());
};