先看一个async/await 范例,以下代码会每隔1s输出一个数字。 那async 和 await 为何如此神奇,能把异步的代码写的看起来像同步的代码?
const delayer = t => new Promise(resolve => setTimeout(resolve, t)) async function start() { console.log(1) await delayer(1000) console.log(2) await delayer(1000) console.log(3) }
先从generator函数说起。
Generator 函数是 ES6 提供的一种异步编程解决方案,也是刚刚接触的同学难以理解的点之一, 当然这里也会先用一些简单的范例做引导便于大家去理解
先看一个范例:
function fn(a,b){ console.log('fn..') return a + b } function* gen(x) { console.log(x) let y = yield fn(x,100) + 3 console.log(y) return 200 }
上述声明了一个普通函数 fn,和一个 Generator 函数 gen,先执行如下代码
let g = gen(1)
调用Generator 函数,返回一个存储状态对象的引用,这个时候 gen 这个函数是没执行的,所以当你执行上面这行代码不会有任何输出
console.log( g.next() )
当调用 g.next() 时,gen 函数开始执行,执行到第一个yield 为止,并把 yield 表达式的值作为状态对象的值。更具体一点,上例先输出 x 也就是 1 ,然后执行 fn(x, 100) 输出 fn.. 并返回101, 然后加3。这时候停止执行,把结果103赋值给状态对象 g,g 的结果变 {value: 103, done: false}。需要注意,yied表达式的优先级极其低, yield fn(x,100) + 3 相当于 yield (fn(x,100) + 3)
console.log( g.next() )
再次执行 g.next() 的时候,代码由上次暂停处开始执行,但此时 yield 表达式的值并不是使用刚刚计算的结果,而是使用 g.next 的参数 undefined , 所以 y的值变为 undefined ,输出 undeined 。执行到 return 200 时,状态对象知道执行结束了,会把return的200赋值到状态对象,结果为 { value: 200, done: true }
如何把刚刚计算的中间值103给下个yield来用呢?我们可以这样
let g = gen(1) g.next(g.next().value)
面做个个简单的优化,让Generator自动调用,知道状态变为done,原理和执行过程在文章最下方视频讲解的链接里
function run(gFun, ...initValues) { let gen = gFun(...initValues) function next(data) { let result = gen.next(data) if (result.done) { console.log(result) return } next(result.value) } next() } run(gen, 1) function fn(a,b){ console.log('fn..') return a + b } function* gen(x) { console.log(x) let y = yield fn(x,100) + 3 console.log(y) return 200 }
测试一下一开始的例子
function run(gFun, ...initValues) { let gen = gFun(...initValues) function next(data) { let result = gen.next(data) if (result.done) return result.value.then(data => next(data)) } next() } const delayer = t => new Promise(resolve => setTimeout(resolve, t)) function* start() { console.log(1) yield delayer(1000) console.log(2) yield delayer(1000) console.log(3) } run(start)
再加上错误处理
function run(gFun, ...initValues) { let gen = gFun(...initValues) function next(data) { return new Promise((resolve, reject) => { let result = gen.next(data) if (result.done) return result.value.then(data => next(data)).catch(e => reject(e)) }) } return next() } const delayer = t => new Promise((resolve, reject) => { setTimeout(() => { if(Math.random() > 0.5) resolve(t) else reject('wrong') }, t) }) function* start() { console.log('start') let t = yield delayer(1000) console.log(t) let t2 = yield delayer(2000) console.log(t2) } run(start).catch(err => console.log(err))
基本上和async / await 语法一致了。