• JS异步编程 (2)


    上篇文章我们讲了下JS异步编程的相关知识,比如什么是异步,为什么要使用异步编程以及在浏览器中JS如何实现异步的。
    最后我们捎带讲了几种JS异步编程模式(回调,事件和发布/订阅模式),这篇我们继续去深入了解下其他的几种异步编程模式。

    其实这几个函数用来解决,异步中 回调函数嵌套问题 (callback hell) 回调地狱

    Promise

    Promise是ES6推出的一种异步编程的解决方案。其实在ES6之前,很多异步的工具库就已经实现了各种类似的解决方案,而ES6将其写进了语言标准,统一了用法。Promise解决了回调等解决方案嵌套的问题并且使代码更加易读,有种在写同步方法的既视感。

    我们先来简单了解下ES6中Promise的用法

    var p = new Promise(function async(resolve, reject){
        // 这里是你的异步操作
        setTimeout(function(){
            if(true){
                resolve(val);
            }else{
                reject(error);
            }
        }, 1000)
    })
    
    p.then(function(val){
        console.log('resolve');
    }, function(){
        console.log('reject');
    })

    首先,ES6规定Promise是个构造函数,其接受一个函数作为参数如上面代码中的async函数,此函数有两个参数,resolve、reject分别对应成功失败两种状态,我们可以选择在不同时候执行resolve或者reject去触发下一个动作,执行then方法里的函数。

    我们可以简单对比下回调的写法和promise的写法的不同

    对于传统回调写法来说,一般会写成这样

    asyncFn1(function () {
      asyncFn2(function() {
        asyncFn3(function() {
            // xxxxx
        });
      });
    });

    或者我们将各个回调函数拆出来独立来写以减少耦合,像是这样:

    function asyncFn1(callback) {
        return function() {
            console.log('asyncFn1 run');
            setTimeout(function(){
                callback();
            }, 1000);
        }
    }
    
    function asyncFn2(callback) {
        return function(){
            console.log('asyncFn2 run');
            setTimeout(function(){
                callback();
            }, 1000);
        }
    }
    
    function normalFn3() {
        console.log('normalFn3 run');
    }
    
    asyncFn1(asyncFn2(normalFn3))()

    最后我们看下Promise的写法

    function asyncFn1() {
        console.log('asyncFn1 run');
        return new Promise(function(resolve, reject) {
            setTimeout(function(){
                resolve();
            }, 1000)
        })
    }
    
    function asyncFn2() {
        console.log('asyncFn2 run');
        return new Promise(function(resolve, reject) {
            setTimeout(function(){
                resolve();
            }, 1000)
        })
    }
    
    function normalFn3() {
        console.log('normalFn3 run');
    }
    
    asyncFn1().then(asyncFn2).then(normalFn3);

    这样来看无论是第一种还是第二种写法,都会让人感到不是很直观,而Promise的写法更加直观和语义化。

    Generator

    Generator函数也是ES6提供的一种特殊的函数,其语法行为与传统函数完全不同。

    我们先直观看个Generator实际的用法

    function* oneGenerator() {
      yield 'Learn';
      yield 'In';
      return 'Pro';
    }
    
    var g = oneGenerator();
    
    g.next();   // {value: "Learn", done: false}
    g.next();   // {value: "In", done: false}
    g.next();   // {value: "Pro", done: true}

    Generator函数是一种特殊的函数,他有这么几个特点:

    • 声明时需要在function后面加上*,并且配合函数里面yield关键字来使用。

    • 在执行Generator函数的时候,其会返回一个Iterator遍历器对象,通过其next方法,将Generator函数体内的代码以yield为界分步执行

    • 具体来说当执行Generator函数时,函数并不会执行,而是需要调用Iterator遍历器对象的next方法,这时程序才会执行从头或者上一个yield之后 到 到下一个yield或者return或者函数体尾部之间的代码,并且将yield后面的值,包装成json对象返回。就像上面的例子中的{value: xxx, done: xxx}

    • value取的yield或者return后面的值,否则就是undefined,done的值如果碰到return或者执行完成则返回true,否则返回false。

    我们知道了简单的Generator函数的用法以后,我们来看下如何使用Generator函数进行异步编程。

    首先我们先来看下使用Generator函数能达到怎样的效果。

    // 使用Generator函数进行异步编程
    function* oneGenerator() {
      yield asyncFn1();
      yield asyncFn2();
      yield normalFn3();
    }
    
    // 我们来对比一下Promise
    asyncFn1().then(asyncFn2).then(normalFn3);

    我们可以看出使用Generator函数进行异步编程更像是在写同步任务,对比Promise少了很多次then方法的调用。

    好,那么接下来我们就来看下如何实际使用Generator函数进行异步编程。

    这里我要特别说明一下,事实上Generator函数不像Promise一样是专门用来解决异步处理而产生的,人们只是使用其特性来产出了一套异步的解决方案,所以使用Generator并不像使用Promise一样有一种开箱即用的感觉。其更像是在Promise或者回调这类的解决方案之上又封装了一层,让你可以像上面例子里一样去那么写。

    我们还是具体来看下上面的例子,我们知道单写一个Generator是不能运行的对吧,我们需要执行他并且使用next方法来让他分步执行,那么什么时候去调用next呢?答案就是我们需要在异步完成时去调用next。我们来按照这个思路补全上面的例子。

    var g;
    
    function asyncFn() {
        setTimeout(function(){
            g.next();
        }, 1000)
    }
    
    function normalFn() {
        console.log('normalFn run');
    }
    
    function* oneGenerator() {
      yield asyncFn();
      return normalFn();
    }
    
    g = oneGenerator();
    
    g.next();
    
    // 这里在我调用next方法的时候执行了asyncFn函数
    // 然后我们的希望是在异步完成时自动去再调用g.next()来进行下面的操作,所以我们必须在上面asyncFn函数体内的写上g.next(); 这样才能正常运行。
    
    // 但其实这样是比较奇怪的,因为当我定义asyncFn的时候其实是不知道oneGenerator执行后叫什么名儿的,即使我们提前约定叫g,但这样asyncFn就太过于耦合了,不仅写法很奇怪而且耦合太大不利于扩展和重用。反正总而言之这种写法很不好。

    那么怎么解决呢,我们需要自己写个方法,能自动运行Generator函数,这种方法很简单在社区里有很多,最著名的就是大神TJ写的co模块,有兴趣的同学可以看下其源码实现。这里我们简单造个轮子:

    // 如果我们想要去在异步执行完成时自动调用next就需要有一个钩子,回调函数的callback或者Promise的then。
    
    function autoGenerator(generator){
      var g = generator();
    
      function next(){
        var res = g.next();  // {value: xxx, done: xxx}
    
        if (res.done) {
            return res.value;
        }
    
        if(typeof res.value === 'function'){    // 认为是回调
            res.value(next);
        }else if(typeof res.value === 'object' && typeof res.value.then === 'function'){     // 认为是promise
            res.value.then(function(){
                next();
            })
        }else{
            next();
        }
      }
    
      next();
    }
    
    // ----
    function asyncFn1(){
        console.log('asyncFn1');
        return new Promise(function(resolve){
            setTimeout(function(){
                resolve();
            }, 1000)
        })
    }
    
    function asyncFn2() {
        console.log('asyncFn2');
        return function(callback){
            setTimeout(function(){
                callback();
            }, 1000);
        }
    }
    
    function normalFn() {
        console.log('normalFn');
    }
    
    function* oneGenerator() {
      yield asyncFn1();
      yield asyncFn2();
      yield normalFn();
    }
    
    autoGenerator(oneGenerator);

    这个方法我们简单实现了最核心的部分,有些判断可能并不严谨,但大家理解这个思路就可以了。有了这个方法,我们才可以方便的使用Generator函数进行异步编程。

    Async/Await

    如果你学会了Generator函数,对于Async函数就会很容易上手。你可以简单把Async函数理解成就是Generator函数+执行器。我们就直接上实例好了

    function asyncFn1(){
        console.log('asyncFn1');
        return new Promise(function(resolve){
            setTimeout(function(){
                resolve('123');
            }, 2000)
        })
    }
    
    function asyncFn2() {
        console.log('asyncFn2');
        return new Promise(function(resolve){
            setTimeout(function(){
                resolve('456');
            }, 2000)
        })
    }
    
    async function asyncFn () {
        var a = await asyncFn1();
        var b = await asyncFn2();
    
        console.log(a,b)
    }
    
    asyncFn();
    
    // asyncFn1
    // asyncFn2
    // 123,456

    当然async里实现的执行器肯定是跟咱们上面简单实现的有所不同,所以在用法上也会有些注意的点

    • 首先async函数的返回值是一个Promise对象,不像是generator函数返回的是Iterator遍历器对象,所以async函数执行后可以继续使用then等方法来继续进行下面的逻辑

    • await后面一般跟Promise对象,async函数执行时,遇到await后,等待后面的Promise对象的状态从pending变成resolve的后,将resolve的参数返回并自动往下执行直到下一个await或者结束

    • await后面也可以跟一个async函数进行嵌套使用。

    对于异步来说,还有很多的知识点我们没有讲到,比如异常处理,多异步并行执行等等,这篇和上篇文章主要还是希望大家对异步编程有个直观的了解,清楚各种解决方案之间的区别和优劣。

    转 :  https://www.cnblogs.com/learninpro/p/9271813.html 

  • 相关阅读:
    第七周学习总结
    深入理解计算机系统第二章家庭作业
    第六周学习总结
    第五周学习总结
    第四周学习总结
    第三周学习总结
    第二周学习总结
    caffe的特殊层
    Eltwise层解析
    slice层解析
  • 原文地址:https://www.cnblogs.com/fps2tao/p/10825816.html
Copyright © 2020-2023  润新知