async/await 函数

9/29/2021 ES6

# 前言

对于我而言来说,在刚接触异步编程的时候,还沉浸在 ajax 请求更新数据的喜悦中,然而下一个需求,我就发现需要依赖上一个请求响应的数据来完成它,两个回调函数的堆叠,让我意识到,会有多个回调函数堆叠的情况吗?回答是的,这样会使我们陷入callback hell回调地狱。当我向组长咨询这个问题时,他向我展示了 async/await 这种写法,完美的解决了目前这种回调函数写法所带来的的困扰。

# async函数是什么呢?

对于刚接触async/await的同学来说,肯定会困惑他到底是如何实现的?可能还会有这样的疑问,如果有多个 await他们到底是并行执行的还是串行?下面我们来解答这些问题。对于看过或者是研究过 co 函数的同学,我们是不是就能发现,对于 async/await 来说,它就像是 co函数 包裹的 Generator的变体,只不过 yeild => await* => asyncco(run) => run。都猜想到这了,async函数是什么呢?它就是Generator的一种变体而已

# async函数带来了哪些优点

  1. 内置执行器,其实这点与 co函数所带来的是一样的,只不过 async/await 这种语法糖,进一步优化了这个问题。

  2. 更好的语义,比起 *generator这种方式语义更好;async表示异步,await表示后面的任务要等一等。

  3. 适用性更好

# async函数实现原理

function sapwn (genFn) {
  return new Promise(function(resolve, reject) {
    const gen = genFn()
    function next (data) {
      const t = gen.next(data)
      if (!t.done) {
        // Tip: resolve res
        t.value.then((res) => next(res)).catch(err => reject(err))
        // Promise.resolve(t.value).then((res) => next(res)).catch(err => reject(err))
      } else {
        resolve(t.value)
      }
    }
    next()
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

或者

function sapwn (genFn) {
  return new Promise(function(resolve, reject) {
    const gen = genFn()
    function next (fn) {
      const t = fn()
      if (!t.done) {
        // Tip: resolve res
        t.value.then((res) => next(
          function() { return gen.next(res) }
        )).catch(err => reject(err))
      } else {
        resolve(t.value)
      }
    }
    next(function() { return gen.next() })
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

阮一峰老师版本:


function spawn(genF) {
  return new Promise(function(resolve, reject) {
    var gen = genF();
    function step(nextF) {
      try {
        var next = nextF();
      } catch(e) {
        return reject(e); 
      }
      if (next.done) {
        return resolve(next.value);
      } 
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });      
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 注意点

但是,如果将 forEach 方法的参数改成 async 函数,也有问题。

async function dbFuc(db) {
  let docs = [{}, {}, {}]

  // 可能得到错误结果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}
1
2
3
4
5
6
7
8

上面代码可能不会正常工作,原因是这时三个 db.post 操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用 for 循环。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  for (let doc of docs) {
    await db.post(doc);
  }
}
1
2
3
4
5
6

如果确实希望多个请求并发执行,可以使用 Promise.all 方法。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  Promise.all(docs.map((doc) => db.post(doc))).then(
    results => console.log(results)
  )
}
// or
async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 总结

  1. async/await 就是 generator + promise 的语法糖,并且上下使用 await 他一定是串行执行的。(疑问:可以使他们并行执行吗?在不是用 Promise.all)
  2. Promise.all 是可以进行请求并发的,但是浏览器对同一域名的最大请求数是有控制的,那门如何来实现最大请求数控制呢?
上次更新: 2/15/2025, 2:29:28 PM