站点工具

用户工具


手写async/await

先看一个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

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 语法一致了。

若愚 · 2023/02/09 14:28 · 手写async_await.txt