Javascript异步编程从回调函数、promise、generator、async/await经历多次演化,终于摆脱了回调地狱。相信各位大佬们,日常代码编写使用promise和async组合来编写异步。
这篇文章不是基础知识概念的普及,假如你还对上述API使用和概念不够熟稔,请先阅读回顾学习下阮一峰老师es6,接下来让我们来一探generator和async/await的实现原理。
阅读完文章你将会了解 Javascript中的协程是如何实现的?
首先,我们定义了两个异步操作,这里为了方便,统一用setTimeout来代替,实际代码应用中可能是网络请求、读写文件、操作数据库等等
异步操作
// 异步操作1
function fetch1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1)
resolve(1)
}, 1000)
})
}
// 异步操作2
function fetch2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(2)
resolve(2)
}, 2000)
})
}
Generator
Generator 函数是 ES6 提供的一种异步编程解决方案,async和await在其不久之后出现了,所以日常编写代码大家都会比较少的使用generator来进行异步编程。
generator函数调用后并不会立即执行,而是返回一个遍历器对象iterator。该对象内部维护了一个状态机。通过调用next方法,来执行yield后操作。
function* getUrl() {
yield fetch1()
yield fetch2()
yield 3
return 4
}
const gen = getUrl();
gen.next() // 1
gen.next() // 2
gen.next() // 3
gen.next() // 4
Async/Await
async 函数是 Generator 函数的语法糖, 使得我们的generator函数可以自动遍历执行。不用在手动调用next方法
async function getUrl() {
await fetch1()
await fetch2()
await 3
return 4
}
getUrl()
async函数是如何实现的呢?下面是Babel转译后代码实现。注意为了方便理解,我们先把定义的async getUrl转为了generator函数。
// 转为generator函数
function* getUrl_() {
yield fetch1()
yield fetch2()
yield 3
return 4
}
// 递归调用,执行next,理解Promise.resolve是关键
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
const info = gen[key](arg)
const value = info.value
if (info.done) {
resolve(value)
} else {
Promise.resolve(value).then(_next, _throw)
}
} catch (error) {
reject(error)
}
}
function asyncToGenerator(fn) {
return function () {
let self = this
let args = arguments
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args)
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value)
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err)
}
_next(undefined)
});
}
}
let getUrl= asyncToGenerator(getUrl_)
通过上面的代码,我们理解了async函数语法糖是如何包装generator函数的。那么Generator是如何实现的呢?下面代码是babel转译后的简化
(代码简化版,并不严谨。但是能让我们很好的理解generator实现的本质)
// 存储内部状态
function Context(){
this.prev = 0
this.next = 0
this.args = undefined
this.done = !1
}
Context.prototype.stop = funtion() {
this.done = !0
}
const context = new Context()
function Generator() {}
// 包装函数
function wrap(innerFn) {
Generator.prototype.next = function() {
const info = innerFn(context)
return { value: info, done: context.done }
}
Generator.prototype.throw = function() {}
return Generator
}
// 代替上面asyncToGenerator中的getUrl_定义
getUrl_ = wrap(function(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return fetchR();
case 2:
_context.next = 4;
return fetchY();
case 4:
_context.next = 6;
return 4;
case 6:
return _context.abrupt("return", 5);
case 7:
case "end":
return _context.stop();
}
}
})
结合这两个部分,我们窥探到了async、await内部机制。同时也理解了generator函数是如何实现暂停、交出执行权的。
generator函数就是javascript对协程的实现。