zhuanyongxigua / blog

blog
77 stars 9 forks source link

JavaScript异步编程之从Generator到async/await #23

Open zhuanyongxigua opened 6 years ago

zhuanyongxigua commented 6 years ago

我的github博客 https://github.com/zhuanyongxigua/blog

ES6之前,JS的函数只能一口气执行到底,Generator的出现使得JS的函数可以做到停顿,根据需要一步步执行。如果没有Generator,可以用闭包来模拟,可这种实现并不方便。

关于generator的运行机制,可以将它简单的总结为三点,详细信息见mdn

  1. 是调用生成器函数的时,函数内部代码不会马上执行,它返回的是一个迭代器对象。
  2. 用返回的迭代器对象调用next方法执行到第一个yield为止,返回yield后面的表达式的值。
  3. 通过next传入的参数,是作为上一条执行的yield表达式的返回值(所以首次调用next方法时传参无意义)。

对于Generator的应用场景,最合适的应当就是对异步嵌套的优化了。

一段来自《YDKJS》很精彩的代码:

function foo(x,y) {
  ajax(
    "http://some.url.1/?x=" + x + "&y=" + y,
    function(err,data){
      if (err) {
        it.throw( err );
      }
      else {
        // 关键的地方,在回调函数中再次调用next方法
        it.next( data );
      }
    }
  );
}

function *main() {
  try {
    var text = yield foo( 11, 31 );
    console.log( text );
  }
  catch (err) {
    console.error( err );
  }
}

var it = main();

// 开始执行,发出请求
it.next();

在调用了next方法之后,请求发出,在请求的回调函数里面return没有意义,拿不到返回值,所以在回调里面再调用一次next方法,并把返回的数据当做参数传入。非常巧妙的运用了Generator的特性。

Generator对异步写法的优化非常优秀,比Promise更好,但promise解决的不光是可读性的问题,还有信任问题

所以,我们需要用Generator + promise的模式。

基本版:

function foo(x,y) {
  return new Promise(function(resolve, reject) {
    ajax("http://some.url.1/?x=" + x + "&y=" + y, function(data){
      resolve(data);
    });
  })
}

function *main() {
  try {
    var text = yield foo( 11, 31 );
    console.log( text );
  }
  catch (err) {
    console.error( err );
  }
}

var it = main();

var p = it.next().value;

p.then(
  function(text){
    it.next( text );
  },
  function(err){
    it.throw( err );
  }
);

这个基础的版本,如果main函数里面有多个步骤,每次调用next方法之后,还要再手写一个Promise链,可以自己封装一个runner方法,一口气把Generator执行到底。

总结一下Generator:它对异步可读性的优化比Promise更好,可有了async/await之后,我也不知道generator还有什么用了(对于模拟多线程之类的应用,我想象不出会有大量应用的场景)。Generator更像是一个过渡性的工具。

async/await

从Generator的思路过来,async/await的方式更像是对Generator的进一步简化。async/await的写法,不需要封装runner也可以执行到底。

function foo(x,y) {
  return new Promise(function(resolve, reject) {
    ajax("http://some.url.1/?x=" + x + "&y=" + y, function(data){
      resolve(data);
    });
  })
}

async function main() {
  try {
    var text = await foo( 11, 31 );
    console.log( text );
  }
  catch (err) {
    console.error( err );
  }
}

main();

此外需要注意的是要正确使用await,没有相互依赖关系的异步操作,不要在同一个async函数里面用await,具体参考How to escape async/await hell

参考资料: