异步历史 回调函数-Promise-generator-async await

144 阅读4分钟

我正在参与掘金创作者训练营第5期,点击了解活动详情

异步

  • 一个任务分为两段来执行
  • 比如读取一个文件 读取文件是需要时间的 那么就可以在读取文件的时候进行其他任务
  • 等到读取完文件 在接着执行刚才还没有执行完的操作
  • 这种不连续的执行 就叫异步。相反 连续的 就叫同步

回调函数

  • 回调函数,就是把任务的第二段单独写在一个函数里,等到重新执行这个任务的时候,就直接调用这个函数。callback => 重新调用

  • 比如读取文件 可以这样写

    • fs.readFile('/text', function (err, data) {
        if (err) throw err
        console.log(data)
      })
      

Promise

  • 回调函数本身没有问题,问题是回到函数的嵌套,也就是经常提到的回调地狱

  • 如果依次读取多个文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展。

  • 所以 promise 的出现就是为了解决上述问题。但是这并不是一种新的语法功能,而是一种新的写法。

    • readFile(fileA)
      .then(res => {
        console.log(res)
      })
      .then(res => {
      ​
      })
      
  • 所以 promise 的写法只是回调函数的改进,使用 .then() 方法以后,异步任务的两段可以看的更加清楚

    • 就是第一段执行的返回结果得到后传给 then 然后 then 方法中执行的是第二段
  • 存在的问题是 代码冗余了 会有很多 .then 并且每个函数都要使用 promise 包裹一下。但是封装一些 axios 请求的时候会非常方便

generator 函数

  • Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)

    • function* gen(x){
        var y = yield x + 2;
        return y;
      }
      
    • 使用 * 来区别于 普通函数 * 和空格 位置 不做要求
  • 整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明

    • 其实也就是一个生成器 (可以去看看 js 红宝书 生成器和迭代器这一块)
    • Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。

async 函数是什么

  • async 函数就是 Generator 函数的语法糖
  • var fs = require('fs');
    ​
    var readFile = function (fileName){
      return new Promise(function (resolve, reject){
        fs.readFile(fileName, function(error, data){
          if (error) reject(error);
          resolve(data);
        });
      });
    };
    ​
    var gen = function* (){
      var f1 = yield readFile('/etc/fstab');
      var f2 = yield readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    ​
    var asyncReadFile = async function (){
      var f1 = await readFile('/etc/fstab');
      var f2 = await readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    
  • 比较就会发现 Generator 函数就是将 * 替换成 async,yield 替换成 await

async 函数的实现

  • async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里

    • async function fn(args){
        // ...
      }
      ​
      // 等同于function fn(args){ 
        return spawn(function*() {
          // ...
        }); 
      }
      
    • 所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器

    • 而 spawn 函数是基于 promise 函数实现的

      • unction 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 + 函数名 是用来定义一个异步函数的 得到一个 promise 对象

    • 函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句
  • await + 任意函数表达式 一般使用 promise 表达式

    • 它的返回值是 promise.resolve() 或者 promise.reject()

    • 因为会返回错误信息 promise.reject() 所以最好用 try catch 捕获一下

      • 就是 await 使用 try catch 捕获一下
      • 作用是 不会导致程序中断 即使出错了也会继续执行下去
  • 例子

    • async function getStockPriceByName(name) {
        var symbol = await getStockSymbol(name); // 两个异步函数 返回的是 promise 对象 通过 await 获取结果
        var stockPrice = await getStockPrice(symbol);
        return stockPrice; // 返回的是一个 Promise 对象
      }
      ​
      getStockPriceByName('goog').then(function (result){
        console.log(result);
      });
      ​
      
    • 上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。
    • function timeout(ms) {
        return new Promise((resolve) => {
          setTimeout(resolve, ms);
        });
      }
      ​
      async function asyncPrint(value, ms) {
        await timeout(ms);
        console.log(value)
      }
      ​
      asyncPrint('hello world', 50);
      ​
      
    • 上面代码指定50毫秒以后,输出"hello world"
  • 总结一下写法

    • function timeout(ms) {
        return new Promise((resolve) => {
          setTimeout(resolve, ms);
        });
      }
      ​
      let promiseRes = async foo() {
        let res = await timeout(50) // 返回一个 promise 的异步函数
        return res
      }
      ​
      promiseRes.then(res => {})
      ​
      // 如果外层还有一个 async 函数
      let asyncRes = await foo()
      

优点

  • 属于 es7 的语法 编写方便 提高效率
  • 以后肯定是使用异步方法的多 而使用 async await 可以让异步函数看上去像同步函数
  • 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

补充

  • promise 和 async 的区别
  • 他们做的事情是相同的 paomise 自己的方法会多一些 all race 等
  • async 需要自己定义 catch

promise 原理

参考文献 阮老师