美文网首页
JavaScript异步编程

JavaScript异步编程

作者: 学的会的前端 | 来源:发表于2019-10-09 10:32 被阅读0次

    什么是异步

    所谓"异步",简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。

    ES5的异步编程方法

    • 回调函数
    • 事件监听
    • 发布/订阅
    • Promise 对象

    回调函数

    • 含义
      所谓回调函数(callback),就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。
    • 代码示例
     readflie(/etc,function(err,data){
            if (err) throw err;
            console.log(data);
        })
    
    • 回调函数的缺点:
      当回调函数比较多时,容易出现回调函数的嵌套。(回调函数噩梦)

    Promise 对象

    • promise含义
      它不是新的语法功能,而是一种新的写法
    • 代码示例
    var readFile = require('fs-readfile-promise');
    
    readFile(fileA)
    .then(function(data){
      console.log(data.toString());
    })
    .then(function(){
      return readFile(fileB);
    })
    .then(function(data){
      console.log(data.toString());
    })
    .catch(function(err) {
      console.log(err);
    });
    
    • promise缺点:
      代码冗余。

    协程

    • 协程含义
      多个线程互相协作,完成异步任务。
    • 协程代码
    function asnycJob() {
      // ...其他代码
      var f = yield readFile(fileA);
      // ...其他代码
    }
    //上面代码的函数 asyncJob 是一个协程,它的奥妙就在其中的 yield 命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线。
    
    • 协程优点:
      代码的写法非常像同步操作,去掉yield后,和同步一样。

    ES6的异步编程方法

    Generator函数

    • Generator函数含义:
      Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。它可以实现暂停执行和恢复执行,是实现异步的根本原因。
    • 代码示例
     function *gen(x){ 
            var y = yield x + 1
            return y
        }
    

    运行结果截图


    捕获.PNG
    • Generator函数不同于其他函数的地方
      即执行它不会返回结果,返回的是指针对象。调用指针 a 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句,上例是执行到 x + 1 为止。
    • next方法的作用:
      next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。当done为true时,表示没有下一个阶段。
    • Generator函数的三大特点:
    1. 暂停执行和恢复执行
    2. 函数体内外的数据交换
    3. 错误处理机制。
    • 具体代码和截图
    1. next 方法返回值的 value 属性,是 Generator 函数向外输出数据;next 方法还可以接受参数,这是向 Generator 函数体内输入数据。
    2. Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。
        function *gen(x){
            try{
                var y = yield x + 1
                return y
            }catch(e){
                console.log(e)
            }
        }
    
    捕获.PNG
    • 虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)

    Thunk 函数

    • Thunk 函数含义
      编译器的"传名调用"实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。它是"传名调用"的一种实现策略,用来替换某个表达式。
    • 代码示例
    function f(m){
            return m * 5
        }
    f(x + 4)
    
    
     //thunk函数
    var thunk = function(){
         return x + 4
    }
    function f(thunk){
        return thunk() * 5
    }
    
    • JavaScript 语言的 Thunk 函数
      JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。
       readFile(ileName,callback);
        
        var readFileThunk = Thunk(fileName)
        readFileThunk(callback)
        
        
        var Thunk = functin(fileName){
            return function(callback){
                return readFile(fileName, callback);
            }
        }
    
    • Thunk 函数作用:
      Thunk 函数现在可以用于 Generator 函数的自动流程管理。Thunk 函数真正的威力,在于可以自动执行 Generator 函数
    
    function run(fn) {
      var gen = fn();
    
      function next(err, data) {
        var result = gen.next(data);
        if (result.done) return;
        result.value(next);
      }
    
      next();
    }
    
    run(gen);
    

    co 函数库

    • 什么是co函数库
      co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。
      比如,有一个 Generator 函数,用于依次读取两个文件。
    var gen = function* (){
      var f1 = yield readFile('/etc/fstab');
      var f2 = yield readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    

    co 函数库可以让你不用编写 Generator 函数的执行器。

    var co = require('co');
    co(gen);
    

    上面代码中,Generator 函数只要传入 co 函数,就会自动执行。

    • co 函数库的原理
      Generator 函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

    两种方法可以做到这一点。
    (1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
    (2)Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。

    co 函数库其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个库。使用 co 的前提条件是,Generator 函数的 yield 命令后面,只能是 Thunk 函数或 Promise 对象。

    • co 函数库的源码
        function co(gen){
            var ctx = this
            return new Promise(function(resolve,reject){
                if(typeof gen === 'function') gen = gen.call(ctx)
                if(!gen || typeof gen.next !== 'function') return resolve(gen)
                onFulfilled()
                function onFulfilled(res){
                    var ret
                    try{
                        ret = gen.next(res)
                    }catch(e){
                        retrun reject(e)
                    }
                    next(ret)
                }
            })
        }
        function next(ret) {
            if(ret.done) return resolve(ret.value)
            var value = toPromise.call(ctx,ret.value)
            if(value && isPromise(value)) return value.then(onFulfilled,onRejected)
            return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
                + 'but the following object was passed: "' + String(ret.value) + '"'));
            }
        }
    
    • 并发的异步操作
      co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。
      这时,要把并发的操作都放在数组或对象里面。
    
    // 数组的写法
    co(function* () {
      var res = yield [
        Promise.resolve(1),
        Promise.resolve(2)
      ];
      console.log(res); 
    }).catch(onerror);
    
    // 对象的写法
    co(function* () {
      var res = yield {
        1: Promise.resolve(1),
        2: Promise.resolve(2),
      };
      console.log(res); 
    }).catch(onerror);
    

    async 函数

    • async 函数含义
      async 函数就是 Generator 函数的语法糖。
    var asyncReadFile = async function (){
      var f1 = await readFile('/etc/fstab');
      var f2 = await readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    
    • async 函数的优点
    1. 内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
    var result = asyncReadFile();
    
    1. 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
    2. 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
    • async 函数的实现
      async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
    
    async function fn(args){
      // ...
    }
    
    // 等同于
    
    function fn(args){ 
      return spawn(function*() {
        //spawn 函数为自动执行器 ...
      }); 
    }
    
    • async 函数的用法
      同 Generator 函数一样,async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
    • 注意点
    1. await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。
    2. await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。
    3. 如果确实希望多个请求并发执行,可以使用 Promise.all 方法。

    相关文章

      网友评论

          本文标题:JavaScript异步编程

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