回调函数 ===> Promise 对象 ===> Generator 函数

JavaScript 的 async/await

async 和 await 在干什么

async 是“异步”的简写,而 await 可以认为是 async wait 的简写。
async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
await 只能出现在 async 函数中。

async 起什么作用

async function testAsync() {
return "hello async";
}

const result = testAsync();
console.log(result);


JavaScript 的 async/await : async 和 await 在干什么_生成器

image.png

——输出的是一个 Promise 对象。

c:\var\test> node --harmony_async_await .
Promise { 'hello async' }

async 函数返回的是一个 Promise 对象。

参考文档: ​​https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function​

async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 ​​return​​​ 一个直接量,async 会把这个直接量通过 ​​Promise.resolve()​​ 封装成 Promise 对象。

async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,我们当然应该用原来的方式:​​then()​​链来处理这个 Promise 对象,就像这样

testAsync().then(v => {
console.log(v); // 输出 hello async
});

Generator 生成器函数

顾名思义,它是一个生成器,它也是一个状态机,内部拥有值及相关的状态,生成器返回一个迭代器Iterator对象,我们可以通过这个迭代器,手动地遍历相关的值、状态,保证正确的执行顺序。


JavaScript 的 async/await : async 和 await 在干什么_生成器_02

image.png

如上代码,定义了一个showWords的生成器函数,调用之后返回了一个迭代器对象(即show)

调用next方法后,函数内执行第一条yield语句,输出当前的状态done(迭代器是否遍历完成)以及相应值(一般为yield关键字后面的运算结果)

每调用一次next,则执行一次yield语句,并在该处暂停,return完成之后,就退出了生成器函数,后续如果还有yield操作就不再执行了.

换言之,next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。

function* showWords() {
yield 'one';
yield 'two';
return 'three';
}

var show = showWords();

调用 Generator 函数,会返回一个内部指针(即遍历器 )g 。这是 Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针 g 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句,上例是执行到 'three'为止。


JavaScript 的 async/await : async 和 await 在干什么_迭代器_03

image.png JavaScript 的 async/await : async 和 await 在干什么_生成器_04

image.png JavaScript 的 async/await : async 和 await 在干什么_迭代器_05

image.png

Generator 函数,依次读取两个文件。

var fs = require('fs');

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

写成 async 函数,就是下面这样。

var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

其中,readFile 函数定义如下:

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

一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。

async 函数的实现

async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。

async function fn(args){
// ...
}

等同于

function fn(args){ 
return spawn(function*() {
// ...
});
}

所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。

下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。

function spawn(genF) {
return new Promise(function(resolve, reject) {
var gen = genF();
function step(nextF) {
try {
var next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}

async 函数是非常新的语法功能,新到都不属于 ES6,而是属于 ES7。目前,它仍处于提案阶段,但是转码器 Babel 和 regenerator 都已经支持,转码后就能使用。

yield与异步

函数在遇到yield后暂停运行,我们可以在需要的地方使用next让它继续运行。并且必要时可以使用next传入参数。

yield 关键字用来暂停和继续一个生成器函数。我们可以在需要的时候控制函数的运行。

yield 关键字使生成器函数暂停执行,并返回跟在它后面的表达式的当前值。与return类似,但是可以使用next方法让生成器函数继续执行函数yield后面内容,直到遇到yield暂停或return返回或函数执行结束。

for…of

for…of循环可以自动遍历Generator函数时生成的Iterator对象。

function* ge() { 
yield '1';
yield '2';
yield '3';
return '4';
}
for(let v of ge()){
alert(V); // 1 2 3 4
}