JS async/await
来源:davidhuangdw     阅读:629
动风网络
发布于 2018-12-01 23:41
查看主页

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}

思考题:如何用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 generatorfunction *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}`));
免责声明:本文为用户发表,不代表网站立场,仅供参考,不构成引导等用途。 系统环境 软件环境
相关推荐
遇见Python(二):面向对象
el-input 标签只能输入纯数字
Java领域唯一全球好评书籍,《Java编程思想(第四版)》值得学习
SQL入门理解基本概述
SwiftUI 2020年开源项目和教程合集
首页
搜索
订单
购物车
我的