前言
说到异步编程方法,熟知的可能有回调函数、Promise、async/await三种解决方案。
回调函数:把任务的第二段单独写在一个函数里,等需要重新执行这个任务时,就直接调用这个函数。
Promise:拆解实现 Promise 及其周边 。详细讲解点击这里
async/await:ES2017标准引入,是本文的重点,下面细扣一下。
首先,async函数是Generator函数的语法糖。Generator又是什么?
Generator是ES6提供的异步编程解决方案,语法行为与传统函数完全不同。传统函数一旦开始运行,就会运行到最后或遇到return时结束,运行期间不能打断,也不能外部传值进函数体内。而Generator函数打破固有思维,使打破函数完整运行成为可能。
文章内容是整合参考中的两本书,提取重要的信息点,方便路过的朋友快速掌握。
Generator 函数
协程
模式上,Generator函数类似协程,多个线程互相协作,完成异步任务。其运行流程大致如下。
- 第一步,协程 A 开始执行。
- 第二步,协程 A 执行到一半,进入暂停,执行权转移到协程 B。
- 第三步,协程 B 交换执行权。
- 第四步,协程 A 恢复执行。 上面流程的协程 A,就是异步任务,分成两段执行。
异步读取文件路径例子如下。
function* asyncReadFile() {
...
let file = yield readFile(fileA)
...
}
上面代码函数asyncReadFile是一个协程,yield命令表示执行到此处,执行权转交给其他协程,等执行权返回,再从暂停的地方继续往后执行。最大的优点,就是代码的写法非常像同步操作。
特征
形式上 ,Generator函数是一个普通函数,但有两个特征。
function与函数名之间有个*。- 函数体内可用
yield语句定义不同的内部状态。
基本使用:
function* generator() {
yield 'hello'
yield 'world'
return 'hello world'
}
let iterator = generator()
iterator.next() // {value: "hello", done: false}
iterator.next() // {value: "world", done: false}
iterator.next() // {value: "hello world", done: true}
iterator.next() // {value: undefined, done: true}
调用Generator函数返回一个iterator迭代器对象,代表Generator函数的内部指针。每调用iterator对象的next方法,就会返回一个带有value和done属性的对象,value代表当前内部状态值,done表示遍历是否结束。
yield表达式
yield是Generator函数的暂停标志, 仅在Generator函数中使用,用在其他地方会报错。
(function (){
yield 1;
})()
// SyntaxError: Unexpected number
yield 表达式如果用在另一个表达式中,必须放在圆括号里面。
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
yield 表达式用作参数或放在赋值表达式的右边,可以不加括号。
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
yield* 表达式
在默认情况下,调用Generator函数返回的是迭代对象,所以在一个Generator函数中调用另一个Generator函数无法打印出调用Generator中的值。
function* foo() {
yield 'a'
yield 'b'
}
function* bar() {
foo() // 是迭代对象
yield 'c'
yield 'd'
}
let iterator = bar()
for(let value of iterator) {
console.log(value)
}
// c
// d
可稍微改动下。
function* foo() {
yield 'a'
yield 'b'
}
function* bar() {
let iterator = foo()
for(let i of iterator) yield i
yield 'c'
yield 'd'
}
let iterator = bar()
for(let value of iterator) {
console.log(value)
}
// a
// b
// c
// d
当然使用yield*方法更简单。
function* foo() {
yield 'a'
yield 'b'
}
function* bar() {
yield* foo()
yield 'c'
yield 'd'
}
let iterator = bar()
for(let value of iterator) {
console.log(value)
}
// a
// b
// c
// d
Generator函数用法,大致如此。那async/await与Generator函数还有哪些不同?
async/await自带执行器,不需要手动next就能自动执行下一步。async函数的返回值是Promise对象,Generator函数返回的是生成器对象。await函数返回的是Promise的resolve/reject的值。 接下来,就是针对以上三点对Generator函数的封装。
async/await 函数
async 函数的实现原理,就是将Generator函数和自动执行器,包装在一个函数里。await则替换yield关键字。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
})
}
其中spawn函数就是自动执行器。Promise的then是自动执行的关键所在。
function spawn(genF) {
// 返回值是promise
return new Promise(function(resolve, reject) {
const gen = genF(); // 获取最新的迭代器
function step(nextF) {
let next;
// 错误处理
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
// res.value包装为 promise,兼容 yield 后基本类型的情况
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); });
});
}
参考
《ES6 标准入门》
你不知道的Javascript(下卷)
Async / Await / Generator 实现原理