站点工具

用户工具


手写async/await

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

以上代码会每隔1s输出一个数字。 那async 和 await 为何如此神奇,能把异步的代码写的看起来像同步的代码?

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

若愚 · 2021/09/23 11:36 · javascript_手写async.txt