异步

作者: 漂泊的小蘑菇 | 来源:发表于2018-10-15 17:55 被阅读0次

    异步的来源

    js是单线程的语言,所谓单线程即代码一行一行的执行,后面的代码必须等待前面的执行完毕才可以执行,对于普通的耗时短的代码来说可能没有什么问题,但是对于耗时长的一段代码来说,就会造成卡顿,比如说发起一个网络请求,请求的资源何时返回,这个时间是不可预估的,这种情况下会造成卡顿,所以js针对这种情况设计了异步。

    处理异步的几种方式

    回调函数

    var ajax = $.ajax({
        url: '/a/b',
        success: function () {
            console.log('success')
        }
    })
    

    回调函数最常见的就是在ajax请求中,以上请求中传入两个参数,url和success,url是请求的路径,success是一个函数,success不会立马执行,而是等待请求成功之后才执行,这种就称为回调函数。
    原理:
    将回调函数作为参数传递给异步执行的函数,当有结果返回之后再触发回调函数。
    弊端:
    多个回调函数嵌套,会造成回调地狱现象,非常难以阅读和维护。

    promise

    const promise = new Promise(function(resolve, reject) {
      // ... some code
    
      if (/* 异步操作成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });
    
    promise.then(function(value) {
      // success
    }, function(error) {
      // failure
    });
    

    Promise对象是一个构造函数,用来生成promise实例,Promise接受一个函数作为参数,该函数有两个参数,resolve和reject,resolve的作用是将状态由未完成变为成功,reject是将状态变为失败。Promise实例生成之后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
    原理:
    每一个异步任务返回一个promise对象,该对象有一个then方法,允许指定回调函数。
    优点:
    回调函数写成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法。

    Generator

    在讲Generator函数的异步应用之前必须要先弄清楚迭代器和生成器的概念

    iterator基本概念

    iterator即迭代器,它是一种接口,为各种数据结构提供统一的访问机制,只要数据结构部署iterator接口,就可以实现遍历。
    iterator是一个对象,它知道如何去访问集合中的一项,并且跟踪该序列中的当前位置,它提供一个next方法,该方法返回包含了value和done两个属性的对象, value 是当前成员的值,done是一个布尔值,表示遍历是否结束。

    function iterator(array){
        var nextIndex = 0;
        return {
           next: function(){
               return nextIndex < array.length ?
                   {value: array[nextIndex++], done: false} :
                   {done: true};
           }
        };
    }
    
    var it = iterator(['yo', 'ya']);
    console.log(it.next()); // {value: 'yo', done: false}
    console.log(it.next()); // {value: 'ya', done: false}
    console.log(it.next()); // {value: undefined, done: true}
    

    一个数据结构只要具有Symbol.iterator属性就是可迭代的(不管是本身还是原型链上有该属性),Symbol.iterator本身是一个函数,是当前数据结构的遍历器生成函数,当数据需要被迭代时,就会被调用然后返回一个用于在迭代中获得值的迭代器。

    iterator调用场合
    • 解构赋值
    • 扩展运算符
    • yield*
    • 其他场合
      for of
      Array.from
      promise.all()
      promise.race()
      Map()
      Set()

    参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_generators

    GeneratorFunction

    由于自定义迭代器需要显式地维护其内部状态,比较繁琐,所以提供了GeneratorFunction(生成器函数)。
    GeneratorFunction是一个可以作为迭代器工厂的特殊函数。当它被执行时会返回一个新的Generator对象。 如果使用 function*语法,则函数会变成GeneratorFunction。

    //idMaker 是一个GeneratorFunction
    function* idMaker() {
      var index = 0;
      while(true)
        yield index++;
    }
    

    生成器函数在执行时能暂停,后面又能从暂停处继续执行。
    调用一个生成器函数,并不会马上执行内部的语句,而是会返回这个生成器的迭代器对象,当调用该迭代器对象的next方法时会执行内部语句,当遇到yield时就会暂停执行,yield后面紧跟该迭代器要返回的值,如果用的yield* ,则表示将执行权移交给另一个生成器函数,当前生成器暂停执行。

    next()方法返回一个包含value和done两属性的对象,value 属性表示本次 yield 表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有 yield 语句,即生成器函数是否已经执行完毕并返回。调用 next()方法时,如果传入了参数,那么这个参数会作为上一条执行的 yield 语句的返回值。

    function* generator(i) {
      yield i;
      yield 'hello';
      y = yield 'world';
      yield y;
      return 'ending';
    }
    
    var hw = generator(3);  // // "Generator { }"
    hw.next();  // { value:3, done: false }
    hw.next();  // { value: 'hello', done: false }
    hw.next();  // { value: 'world', done: false }
    hw.next(2);  // { value: 2, done: false }
    hw.next();   // { value: 'ending', done: true }
    hw.next();   // { value: undefined, done: true }
    

    当在生成器函数中显式 return 时,会导致生成器立即变为完成状态,即调用 next() 方法返回的对象的 done 为 true。如果 return 后面跟了一个值,那么这个值会作为当前调用 next() 方法返回的 value 值,如果没有则为undefined。

    function* generator() {
      yield i;
      yield 'hello';
      return 2;
      y = yield 'world';
      yield y;
      return 'ending';
    }
    
    var hw = generator();  // "Generator { }"
    hw.next();  // { value: 'hello', done: false }
    hw.next();  // { value: 2, done: true }
    hw.next();  // { value: undefined, done: true }
    
    Generator函数的异步应用

    Generator 函数是协程在 ES6 的实现,协程的意思是多个线程互相协作,完成异步任务,最大特点就是可以交出函数的执行权即暂停执行。
    整个 Generator 函数就是一个封装的异步任务,需要暂停的地方,都用yield语句注明

    var fetch = require('node-fetch');
    
    function* gen(){
      var url = 'https://api.github.com/users/github';
      var result = yield fetch(url);
    }
    
    var g = gen();
    var result = g.next(); // result.value是一个promise
    
    result.value.then(function(data){
      return data.json();
    }).then(function(data){
      g.next(data);
    });
    

    弊端:流程管理不方便,手动调用next方法

    Generator 函数的自动流程管理

    针对Generator 函数的流程管理不方便的弊端,提出了以下几个方法:

    1. 回调函数。 将异步操作包装成Thunk函数,在回调函数里面交回执行权
      前提:每一个异步操作都是都是Thunk函数,即yield命令之后的都要是Thunk函数。
    var fs = require('fs');
    var thunkify = require('thunkify');
    var readFileThunk = thunkify(fs.readFile);  //转换为Thunk函数
    
    // 生成器函数
    var g = function* (){
      var f1 = yield readFileThunk('fileA');
      var f2 = yield readFileThunk('fileB');
      // ...
      var fn = yield readFileThunk('fileN');
    };
    // 自动执行next
    function run(fn) {
      var gen = fn();
      function next(err, data) {
        var result = gen.next(data);
        if (result.done) return;
        result.value(next);
      }
      next();
    }
    
    run(g);
    
    1. Promise对象,将异步操作包装成 Promise 对象,用then方法交回执行权
    var fs = require('fs');
    // 将异步操作包装成 Promise 对象
    var readFile = function (fileName){
     return new Promise(function (resolve, reject){
       fs.readFile(fileName, function(error, data){
         if (error) return 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());
    };
    //用then方法交回执行权,调用next方法
    function run(gen){
     var g = gen();
    
     function next(data){
       var result = g.next(data);
       if (result.done) return result.value;
       result.value.then(function(data){
         next(data);
       });
     }
    
     next();
    }
    
    run(gen);
    
    
    1. co模块
      co模块就是将Thunk函数和Promise对象包装成一个模块,使用co模块的前提是yield之后只能是Thunk函数或者Promise对象,
    var co = require('co');
    // 生成器函数
    var gen = function* () {
      var f1 = yield readFile('/etc/fstab');
      var f2 = yield readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    // co函数返回一个Promise对象,因此可以用then方法添加回调函数
    co(gen).then(function (){
      console.log('Generator 函数执行完成');
    });
    

    参考:
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function*
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield

    async await

    async函数是Generator函数的语法糖,他们的区别在于:

    1. async函数自带内置执行器
      Generator函数的执行必须要靠执行器,所以有了co模块,但是async函数自带执行器。
    2. async函数适用性更广
      使用co模块时,yield后面必须跟Thunk函数或者Promise对象,但是async函数后面可以是 Promise 对象和原始类型的值(跟原始类型值时相当于同步操作)。
    3. 返回值不同
      async函数返回值是Promise对象,Generator函数返回值是Iterator对象。

    实现原理:将 Generator 函数和自动执行器,包装在一个函数里

    const fs = require('fs');
    
    const readFile = function (fileName) {
      return new Promise(function (resolve, reject) {
        fs.readFile(fileName, function(error, data) {
          if (error) return reject(error);
          resolve(data);
        });
      });
    };
    
    const asyncReadFile = async function () {
      const f1 = await readFile('/etc/fstab');
      const f2 = await readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    

    async函数内部return语句返回的值,会成为then方法回调函数的参数, 抛出的错误对象会被catch方法回调函数接收到。

    async function f() {
      await Promise.reject('出错了');
    }
    
    f()
    .then(v => console.log(v))
    .catch(e => console.log(e))
    // 出错了
    

    参考:
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function

    相关文章

      网友评论

          本文标题:异步

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