美文网首页
异步操作和Async函数

异步操作和Async函数

作者: zhulichao | 来源:发表于2020-08-10 14:01 被阅读0次

    异步操作和Async函数

    异步操作

    异步编程的四种方式:回调函数、事件监听、发布/订阅、Promise。

    为什么Node约定,回调函数的第一个参数,必须是错误对象err(如果没有错误,该参数就是null)?原因是执行分成两段,第一段执行完以后,任务所在的上下文环境就已经结束了,在这以后抛出的错误,原来的上下文环境已经无法捕捉,只能当作参数,传入第二段。

    回调函数在多个回调函数嵌套时,代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理,称为"回调函数地狱"。Promise对象就是为了解决这个问题而提出的,将回调函数的嵌套,改成链式调用。

    Promise的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。Promise的最大问题是代码冗余,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚。

    Generator函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。

    使用Generator函数,执行一个异步任务。

    var fetch = require('node-fetch');
    
    function* gen(){
      var url = 'https://api.github.com/users/github';
      var result = yield fetch(url);
      console.log(result.bio);
    }
    /*
     * 执行Generator函数,获取遍历器对象,然后用next方法执行异步任务的第一阶段。
     * 由于Fetch模块返回的是一个Promise对象,因此要用then方法调用下一个next方法。
     */
    var g = gen();
    var result = g.next();
    result.value.then(function(data){
      return data.json();
    }).then(function(data){
      g.next(data);
    });
    

    Thunk函数现在可以用于Generator函数的自动流程管理。下面就是一个基于Thunk函数的Generator执行器。

    function run(fn) {
      var gen = fn();
      // next函数是Thunk的回调函数
      function next(err, data) {
        var result = gen.next(data);
        if (result.done) return;
        result.value(next);
      }
      next();
    }
    
    function* g() {
      // ...
    }
    run(g);
    

    Async函数

    从语法上看,async函数就是将Generator函数的星号(*)替换成async,将yield替换成await。async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

    async函数对 Generator 函数的改进,体现在以下四点:

    • 内置执行器。Generator函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。
    • 更好的语义。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
    • 更广的适用性。co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值。
    • 返回值是 Promise。async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。

    async函数的语法规则总体上比较简单,难点是错误处理机制:

    • async函数返回一个Promise对象。async函数内部return语句返回的值,会成为then方法回调函数的参数。async函数内部抛出错误,会导致返回的Promise对象变为reject状态,抛出的错误对象会被catch方法回调函数接收到。
    • async函数返回的Promise对象,必须等到内部所有await命令的Promise对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
    • 正常情况下,await命令后面是一个Promise对象。如果不是,会被转成一个立即resolve的Promise对象。await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。
    • 只要一个await语句后面的Promise变为reject,那么整个async函数都会中断执行。为了避免这个问题,可以将await放在try...catch结构里面,这样后面的await就会执行;另一种方法是await后面的Promise对象再跟一个catch方法,处理前面可能出现的错误。

    async函数的实现

    async function fn(args){
      // ...
    }
    
    // 等同于
    function fn(args){
      return spawn(function*() {
        // ...
      });
    }
    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); });
      });
    }
    

    async 函数的用法

    async function getStockPriceByName(name) {
      const symbol = await getStockSymbol(name);
      const stockPrice = await getStockPrice(symbol);
      return stockPrice;
    }
    
    getStockPriceByName('goog').then(function (result) {
      console.log(result);
    });
    

    相关文章

      网友评论

          本文标题:异步操作和Async函数

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