JS async/await

作者: davidhuangdw | 来源:发表于2018-12-01 20:23 被阅读8次

async/await: 最简洁地写串行的异步操

我们知道JS是单线程的,IO操作都被设计为异步的(非阻塞的),对于异步操作我们必需要写callback function来处理异步操作的返回结果。

当要写n个串行的IO操作(上一个IO结束后才继续下一个)时, 不得不写n个回调函数:

get('/car/1', car =>{
  get(`/seats/${car.seat}`, seat => {
    get(`/colors/${seat.color}`, color => {
      //...more_IOs
    })
  })
});

n个串行的IO操作,以上代码(都写成匿名回调函数的话)会出现n层嵌套, 可以采用Promise 改进了代码嵌套(callback hell)的问题:

promiseGet('/car/1')
  .then(car => promiseGet(`/seats/${car.seat}`))
  .then(seat => promiseGet(`/colors/${seat.color}`))
  .then(color => {/*return some promise*/})
  .then(...)

Promise加回调函数似乎已经足够简洁了,因为es6里arrow function使得写一个匿名函数变得很简洁。
然而如果使用 es7 async/await, 我们连回调函数都不用写了:

// promise version:
let v1, v2;
promise1()
  .then(v => {v1=v; return promise2(v1)})
  .then(v => {v2=v; return promise3(v1,v2)})
  .then(v3 => { /*..v1,v2,v3...*/ });


// async/await version:
async function asyn_run(){
  let v1 = await promise1();
  let v2 = await promise2(v1);
  let v3 = await promise3(v1, v2);
  /*... v1, v2, v3 ... */
}
async_run();  //actually non-blocking

async/await只是语法糖

注意async function依然是非阻塞的异步执行,async/await只是语法糖,也就是相当于编译器替我们写了回调函数!

理解 async/await 背后的 Generator生成函数

async/await 其实被编译成了 generator函数(function*(){...})):

async function fn(args){
  let v1 = await promise1();
  let v2 = await promise2(v1);
  ...
}

// 等同于

function fn(args){ 
  return spawn(function*() {
    // .... 所有await替换成yield
    let v1 = yield promise1();
    let v2 = yield promise2(v1);
    ...
  }); 
}

es6 提供了Generator 生成函数: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*

function *gen(){
  let i=0, v;
  while(i<2) v=yield i++;
  return i;
}
let g = gen();
g.next();   //{value:0, done:false}
g.next();   //{value:1, done:false}
g.next();   //{value:2, done:true}

编译器会把generator function拆成多个断点(用switch/case,不同断点执行不同case语句),并把当前断点位置存在对象g上以便下次继续:

//上文function *gen(){..}代码被babel编译成了带switch/case普通函数:

var _marked = /*#__PURE__*/regeneratorRuntime.mark(gen);
function gen() {
  var i, v;
  return regeneratorRuntime.wrap(function gen$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          i = 0, v = void 0;

        case 1:
          if (!(i < 2)) {
            _context.next = 7;
            break;
          }

          _context.next = 4;
          return i++;

        case 4:
          v = _context.sent;
          _context.next = 1;
          break;

        case 7:
          return _context.abrupt("return", i);

        case 8:
        case "end":
          return _context.stop();
      }
    }
  }, _marked, this);
}
var g = gen();
g.next(); //{value:0, done:false}
g.next(); //{value:1, done:false}
g.next(); //{value:2, done:true}
  • 对比: 函数与生成函数
    • 函数的调用方式: func(): 从头执行到return -> 返回给caller
    • 生成函数的调用方式: 多次g.next(init_value): 每次从上一次的yield位置(同时将参数init_value传入此位置)执行到下一个yield -> 返回给caller
      • yield 相当于一个断点,跳转到caller, 当caller再执行g.next()时再从此断点继续

思考题:如何用Generator实现 async/await

前面提到async/await 其实被编译成了 generator函数(function*(){...})):

async function fn(args){
  let v1 = await promise1();
  let v2 = await promise2(v1);
  ...
}

// 等同于

function fn(args){ 
  return spawn(function*() {
    // .... 所有await替换成yield
    let v1 = yield promise1();
    let v2 = yield promise2(v1);
    ...
  }); 
}

思考下如何写出spawn函数?

function spawn(generator){
  let g = generator();      // create a generator(object) to run

  return new Promise((final_resolve, final_reject) => {
    // inner closure function with final_resolve/final_reject
    function feed_and_take_one(last_async_value, isError) {
      let this_yield = isError ? g.throw(last_async_value) : g.next(last_async_value);  //feed last_async_value back to last yield point
      let promise = this_yield.value;

      if (this_yield.done)
        final_resolve(promise);
      else
        promise.then(feed_and_take_one, e=>feed_and_take_one(e, true))
    }

    feed_and_take_one();
  });
}

思路:

  1. caller创建一个g=gen()对象,执行g.next() 得到一个yield出来的promise
  2. promise中得到结果resolve_value后, 再次执行g.next(resolve_value)传回生成函数,等待返回下一个promise
    • 也就是在promise的回调中递归调用步骤2
  3. 步骤2中yield value为done状态时,停止(递归)

测试spawn函数:

// manual testing:
function pause_and_return(n){
  return new Promise(resolve =>{
    console.log(`waiting ${n} seconds...`);
    setTimeout(()=>resolve(n), n*1000);
  });
}

function throw_async_error(msg){
  return new Promise((_, reject)=> reject(new Error(msg)));
}

// async/await version:
async function async_routine(){
  try {
    for (let i of [3, 2, 1]) {
      let a = await pause_and_return(i);
      console.log(`-----yield async_value: ${a}`);
    }

    await throw_async_error("my error");
    await pause_and_return(100);              //shouldn't run this after throw
  }catch(e){
    console.log(`------catch error: ${e}`);
    return pause_and_return(6);
  }
}
async_routine().then(v => console.log(v));
console.log("====== non blocking outside async");


// spawn version: async by generator
function *coroutine(){
  try {
    for (let i of [3, 2, 1]) {
      let a = yield pause_and_return(i);
      console.log(`-----yield async_value: ${a}`);
    }

    yield throw_async_error("my error");
    yield pause_and_return(100);              //shouldn't run this after throw
  }catch(e){
    console.log(`------catch error: ${e}`);
    return pause_and_return(6);
  }
}
spawn(coroutine).then(v => console.log(`return final async value: ${v}`));

相关文章

  • es6--async和await

    async 和 await被称为js异步的最终解决方案,那么我们来了解下:async:异步方法, await:等待...

  • js异步编程(updating)

    js 异步编程方式: Promise,generator/yield,async/await 回掉函数 js事件监...

  • async和await

    浅谈Async/Await用 async/await 来处理异步 async和await async:声明一个异步...

  • 目录

    JS篇 Promise Iterator Generator async-await[https://www.ji...

  • JS async/await

    async/await: 最简洁地写串行的异步操 我们知道JS是单线程的,IO操作都被设计为异步的(非阻塞的),对...

  • js async/await

    async await 基本上是Generator、yield的一个封装(实际也就是通过Promise实现)。as...

  • ES8(一) —— async&await

    目录 async和普通函数的区别 await async/await处理多回调异步 async和await必须配合...

  • async

    async/await特点 async/await更加语义化,async是“异步”的简写,async functi...

  • 关于Js的promise、generator、async、awa

    关于Js的promise、generator、async、await 第一章 前言 ​ 大家都...

  • ES6中的好东西

    1 Await/Async 前端的回调时代我没有赶上,我赶上的是await/async时代。await和async...

网友评论

    本文标题:JS async/await

    本文链接:https://www.haomeiwen.com/subject/xgbrcqtx.html