# async、await原理
# thunk函数
为了更加方便的处理异步操作问题,现在最新的前端框架生态都开始用上了es6的Generator和yield,有的甚至已经开始使用 es7的async、await语法了,这两样都是基于Generator自动执行的原理,这里就要说到thunk函数了
// 首先我们来了解下参数求值策略,给出下面代码
var x = 1;
function f(m){
return m * 2;
}
f(x + 5); // x +5 在何时运算?
// 1、传值调用:
var x = 1;
function f(m){
return m * 2;
}
f(6); // 将值先计算出来再作为参数传入函数。C 语言采用这种策略
// 2、传名调用
var x = 1;
function f(m){
return m * 2; // (x + 5) * 2。参数不求值,传到函数中,在函数中进行运算求值。JS 采用此策略
}
f(x + 5);
Thunk 函数的作用是将多参数替换成单参数版本
var Thunk = function (fileName){
return function (callback){
return fs.readFile(fileName, callback);
};
};
// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);
// Thunk版本的readFile(单参数版本)
var readFileThunk = Thunk(fileName);
readFileThunk(callback);
// 任何函数,只要参数有回调函数,就能写成Thunk函数的形式。下面是一个简单的Thunk函数转换器
var Thunk = function(fn) {
return function (...args) {
return function (callback) {
return fn.call(this, ...args, callback);
}
};
};
var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);
# Generator函数
Generator函数的实例。它具有状态值suspended和closed,suspended代表暂停,closed则为结束。但是这个状态是无法捕获 的,我们只能通过Generator函数的提供的方法获取当前的状态,先简单介绍下Generator函数提供了3个方法,next/return/throw
//next方式是按步执行,每次返回一个值,同时也可以每次传入新的值作为计算
function* foo(x) {
let a = yield x + 1;
let b= yield a + 2;
return x + 3;
}
const result = foo(5) // foo {<suspended>}
result.next(1); // {value: 6, done: false}
result.next(2); // {value: 4, done: false} 决定上一个yield是2即a值为2
result.next(3); // {value: 8, done: true}
result.next(4); //{value: undefined, done: true}
throw则根据函数中书写try catch返回catch中的内容,如果没有写try,则直接抛出异常
function* foo(x) {
try{
yield x+1
yield x+2
yield x+3
yield x+4
}catch(e){
console.log('catch it')
}
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.next(); // {value: 2, done: false}
result.throw(); // catch it {value: undefined, done: true}
result.next(); //{value: undefined, done: true}
Generator函数返回值是个带有状态的Generator实例。它可被for of 调用,进行遍历且只可被for of 调用此时将返回他所有状态
function* foo(x) {
console.log('start')
yield x+1
console.log('state 1')
yield x+2
console.log('end')
}
const result = foo(0) // foo {<suspended>}
for(let i of result){
console.log(i)
}
//start
//1
//state 1
//2
//end
result.next() //{value: undefined, done: true}
在Generator函数中,我们有时需要将多个迭代器的值合在一起,我们可以使用yield *的形式,将执行委托给另外一个Generator函数
function* foo1() {
yield 1;
yield 2;
return "foo1 end";
}
function* foo2() {
yield 3;
yield 4;
}
function* foo() {
yield* foo1();
yield* foo2();
yield 5;
}
const result = foo();
console.log(iterator.next());// "{ value: 1, done: false }"
console.log(iterator.next());// "{ value: 2, done: false }"
console.log(iterator.next());// "{ value: 3, done: false }"
console.log(iterator.next());// "{ value: 4, done: false }"
console.log(iterator.next());// "{ value: 5, done: false }"
console.log(iterator.next());// "{ value: undefined, done: true }"
foo在执行的时候,首先委托给了foo1,等foo1执行完毕,再委托给foo2。但是我们发现,”foo1 end” 这一句并没有输出。 在整个Generator中,return只能有一次,在委托的时候,所有的yield*都是以函数表达式的形式出现;return的值是表达 式的结果,在委托结束之前其内部都是暂停的,等待到表达式的结果的时候,将结果直接返回给foo。此时foo内部没有接收的变 量,所以未打印;如果我们希望捕获这个值,可以使用yield *foo()的方式进行获取
# async、await实现
首先我们来编写一个基于thunk函数的Generator
var readFile = function (fileName) {
return function (callback) {
return fs.readFile(fileName, callback)
}
}
let gen = function* () {
let r1 = yield readFile('./package.json')
console.log(r1.toString())
let r2 = yield readFile('./index.js')
console.log(r2.toString())
}
// 我们来手动执行一下这个Generator:
let g = gen()
let r1 = g.next()
r1.value(function (err, data) {
if (err) {
throw err
}
let r2 = g.next(data)
r2.value(function (err, data) {
if (err) {
throw err
}
g.next(data)
})
})
// 可以注意到,在我们手动执行基于thunk函数的Generator时,有很多代码是可以复用的,而所谓的Generator自动执行
// 就是把这些可复用的部分封装成函数,然后让它们递归执行,直到执行完所有的yield
// Generator自动执行器无非就是把可复用的部分封装成next函数,然后让其递归执行,直到执行完所有的yield
function run(fn) {
let gen = fn()
function next(err, data) {
let result = gen.next(data)
if (result.done) {
return
}
result.value(next)
}
next()
}
run(gen)
上面的例子是基于thunk函数的,而即将出现的es7的async、await语法是基于Promise的这里再上一个基于Promise 的Generator的自动执行,这个和基于thunk函数的大同小异,只是把函数返回值的获取权以Promise的方式交出
//包装返回Promise对象的函数
function readFile(fileName) {
return new Promise((resolve, reject) => {
fs.readFile(fileName, (error, data) => {
if (error) {
reject(error)
} else {
resolve(data)
}
})
})
}
// 编写Generator
let gen = function* () {
let r1 = yield readFile('./package.json')
console.log(r1.toString())
let r2 = yield readFile('./index.js')
console.log(r2.toString())
}
// 编写Generator执行器
function run(gen) {
let g = gen()
function next(data) {
let result = g.next(data)
if (result.done) {
return result.value
}
result.value.then((data) => next(data))
}
next()
}
//用Generator执行器自动执行
run(gen)