资讯专栏INFORMATION COLUMN

切图崽的自我修养-[ES6] 异步函数管理方案浅析

jzman / 2936人阅读

摘要:前言业务开发中经常会用到异步函数,这里简单的对异步函数以及它的各种各样的解决方案做一个浅析优缺点优点能够极大的提高程序并发业务逻辑的能力缺点异步函数的书写方式和代码执行逻辑很不直观,回调函数这种方式不太符合人类的的线性思维异步函数的执行流程

前言

业务开发中经常会用到异步函数,这里简单的对异步函数以及它的各种各样的解决方案做一个浅析

优缺点:

优点:

能够极大的提高程序并发业务逻辑的能力.

缺点:

异步函数的书写方式和代码执行逻辑很不直观,回调函数这种方式不太符合人类的的线性思维

异步函数的执行流程通常不好管理

不好对异步函数部署错误处理机制


解决方案

针对异步函数存在的缺点,所以才有了形形色色的异步的处理方案,常见的比如

原生的回调函数

promise/A+

async/await(generator);


业务场景

但这些解决方案各自能解决什么问题,才是我们所关心的.
实际上,如果对业务场景进行抽象,开发过程中对异步函数的管理可以抽象成如下的几种需求
比如有异步函数f1,f2,f3:

对f1,f2,f3之间的执行顺序没有要求. 它们的执行结果不互相依赖,谁先完成谁后完成无关紧要

对f1,f2,f3之间的执行顺序没有要求. 它们的执行结果不互相依赖,谁先完成谁后完成无关紧要. 但有一个函数f4,它必须等到f1,f2,f3执行完毕之后才能执行

对f1,f2,f3之间的执行顺序有要求,必须要满足f1->f2->f3的执行顺序

下面就来简单介绍一下,各个解决方案针对不同的业务场景,能解决什么问题


需求1

对f1,f2,f3执行完成的顺序没有要求,即它们的执行结果是不互相依赖的,我们可以写成如下的形式

    f1(function(){});
    f2(function(){});
    f3(function(){});
    ...
    

需求2

f1,f2,f3之间执行完成的顺序没有要求,即它们各自的执行结果是不互相依赖的,但有一个函数f4,需要等f1,f2,f3函数全部执行完成之后才能执行

解决方法:`维护一个记数器`. f1,f2,f3的执行顺序无关紧要,但对于f1,f2,f3每一个完成的回调里,都要判断是否3个函数都已完成(通过count来判断),如果都已完成,则执行f4.  Ps(这里的写成自执行的形式是防止count被污染)  实际上,node的三方异步管理模块EventProxy, 以及promise的promise.all的实现,都是采用这种方式来对异步函数进行管理的.

    (function(){
        let count = 0;
        function handler(){
            if(count==3){
                f4();
            }
        }
        
        f1(function(){count++; handler();});
        f2(function(){count++; handler();});
        f3(function(){count++; handler();});
    }()

需求3

对于异步函数f1,f2,f3,我想保证它们的执行顺序是f1->f2->f3的顺序(即f1如果执行成功,调用f2,如果f2执行成功,调用f3)

3.1

按最原始的方法,可以写成如下回调嵌套的形式.即把f2作为f1的回调,f3作为f3的回调.依次嵌套就可以满足f1->f2->f3这种调用形式. 这种方法虽然能够满足需求但同时存在很多问题: 回调层级太深不好调试.

最简单的情况,假设不考虑f1,f2,f3出错的情况(即f1,f2,f3全部都执行正确),函数的执行流程大概是这样:

    
    f1(function(){
        f2(function(){
            f3(function(){
                ...
            })
        })
    })

实际上,考虑到各个异步函数都有可能出错的分支, 真实的执行流程应该是这样(这才三层回调嵌套,代码已经完全混乱的不能看了):


    f1(function(){
        if(err){
            //f1 err handler
        }
        else{
            f2(function(){
                if(err){
                    //f2 err handler    
                }
                
                else{
                    f3(function(){
                        if(err){
                            //f2 err handler
                        }
                        else{
                            ...
                        }
                    })    
                }
            
            })
        }
    })
    
        
3.2

为了解决这个嵌套过深这种问题,所以有了promise这种的解决方案. 这种规则逻辑比较清晰,更容易理解,但需要做一点点预备工作. 即异步函数f1,f2,f3全部要先封装成promise规范,这里拿f1举例(f2,f3同理).

   function f1(){
           var promiseObj = new Promise(function(resolve,reject){
            //f1的具体功能代码实现
            ...
            
            if(f1err){ //如果f1执行出错
                reject(failValue);
            }
            else{ //如果f1执行成功
                resolve(successValue);
            }
           })
           return promiseObj;
   }
   

预备工作做完了,我们来看具体实现

    f1()
    .then(function suc(){return f2()},function fail(){/*f1 err handler*/})
    .then(function suc(){return f3()},function fail(){/*f2 err handler*/})
    .then(function suc(){},function fail(){/*f3 err handler*/})

简单来分析下,首先f1()执行完成后,会返回一个promise对象,它会被then捕获,如果promise对象的状态是resolve状态,会调用then的第一个参数,即成功回调. 如果promise对象的状态是reject状态,会调用then的第二个参数,即失败回调.

如果f1执行成功,则会在then中的成功回调suc中调用f2(),而f2()返回的也是一个promise对象,会被下一个then捕获...依次类推

如果f1执行失败,会在then的失败回调fail中调用你写的err handler句柄,然后return跳出整个执行链就可以

我们可以看到promise的语法实际上是将深度嵌套的逻辑通过then的处理平摊了.在这种语法规则下,f1->f2->f3的执行顺序一目了然.当然它还是有缺点的,就像之前提到的,它必须要做一些预备工作,即需要把异步函数要封装成promise规范. 另外,它还有一堆then,看起来有点头晕

3.3

既然promise我们也觉得有点麻烦,那只能试试es7的async/await了,听说async/await+promise是管理异步回调的终极解决方案

首先来明晰下try/catch的概念. 当一个代码片段,我们不能确定它到底能不能成功执行的情况下,就会用try/catch处理. 当fun函数自上到下执行,一开始会进入try{}块,开始执行这个代码片段

一旦try{}块内部某一条代码没有正确执行,则不再执行try{}块内部的代码,而是立马跳出try{}块,同时会抛出一个异常,这个异常会被catch(){}捕获. 开始执行catch{}块里的代码. 我们假设code2出错了,整个函数内部的执行顺序是 code 0 -> code 1 -> code 2-> code 4 -> code 5;

如果try{}块内部的代码片段全都正确执行了.就不会进入catch{}的错误处理流程了. 这时候整个函数内部的执行顺序是 code 0 -> code 1 -> code 2-> code 3 -> code 5;

  
  
          functionfun(){
            
                /* code 0 */

            
                try{
                    /* code 1 */
                    /* code 2 */
                    /* code 3 */                
                }
                catch(err){
                    /* code 4 */
                }
                
                /* code 5 */

            }
            
            fun();
            
            

对应到async上也是同理,async函数有一个特点,它的await能监听一个promise对象. 如果监听到的promise对象是resolve正确态,那么await这条语句相当于是被正确执行了,不会进入catch{}流程. 但如果监听到的promise是reject错误态,则会认为await语句执行失败了,会抛出异常然后跳进catch{}错误处理.

    var funa = function(){
        var promiseObj_a = new Promise(function(resolve,reject){
            setTimeout(function(){resolve(1);},1000);
        });
        return promiseObj_a;
    }
    var funb = function(){
        var promiseObj_b = new Promise(function(resolve,reject){
            setTimeout(function(){resolve(2);},5000)
        });
        return promiseObj_b;
    }
    var func = function(){
        var promiseObj_c = new Promise(function(resolve,reject){
            setTimeout(function(){reject(3);},8000);
        });
        return promiseObj_c;
    }
    
    async function testAsync(){
        
        try {
            var a =await funa();
            console.log(a,"resolve");
        }
        catch(erra){
            console.log(erra,"reject");
        }
        
        try {
            var b =await funb();
            console.log(b,"resolve");
        }
        catch(errb){
            console.log(errb,"reject");
        }
    
        try {
            var c =await func();
            console.log(c,"resolve");
        }
        catch(errc){
            console.log(errc,"reject");
        }
    }
    
    testAsync();
    //输出结果是 
    //1 resolve
    //2 resolve
    //3 reject
    

我们能看到async/await配合promise带来了巨大的好处. 首先异步函数的执行顺序能够像同步一样一眼看出来,简单明了. 其次,针对任何一个异步函数的执行,都有完善的try/catch机制,错误处理非常非常容易.

结言

各种解决方案需要结合对应的业务场景使用

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/50013.html

相关文章

  • 切图崽的自我修养-[ES6] 异步函数管理方案浅析

    摘要:前言业务开发中经常会用到异步函数,这里简单的对异步函数以及它的各种各样的解决方案做一个浅析优缺点优点能够极大的提高程序并发业务逻辑的能力缺点异步函数的书写方式和代码执行逻辑很不直观,回调函数这种方式不太符合人类的的线性思维异步函数的执行流程 前言 业务开发中经常会用到异步函数,这里简单的对异步函数以及它的各种各样的解决方案做一个浅析 优缺点: 优点: 能够极大的提高程序并发业务逻辑的能...

    KavenFan 评论0 收藏0
  • 切图崽的自我修养-[ES6] 异步函数管理方案浅析

    摘要:前言业务开发中经常会用到异步函数,这里简单的对异步函数以及它的各种各样的解决方案做一个浅析优缺点优点能够极大的提高程序并发业务逻辑的能力缺点异步函数的书写方式和代码执行逻辑很不直观,回调函数这种方式不太符合人类的的线性思维异步函数的执行流程 前言 业务开发中经常会用到异步函数,这里简单的对异步函数以及它的各种各样的解决方案做一个浅析 优缺点: 优点: 能够极大的提高程序并发业务逻辑的能...

    godiscoder 评论0 收藏0
  • 切图崽的自我修养-[ES6] 迭代器Iterator浅析

    摘要:任何数据结构只要部署接口,就可以完成遍历操作即依次处理该数据结构的成员。的遍历某个数据结构过程是这样的比如对进行遍历创建一个指针对象,指向当前数组的起始位置。 Iterator 这真是毅种循环 Iterator不是array,也不是set,不是map, 它不是一个实体,而是一种访问机制,是一个用来访问某个对象的接口规范,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Ite...

    neu 评论0 收藏0
  • 切图崽的自我修养-[ES6] 迭代器Iterator浅析

    摘要:任何数据结构只要部署接口,就可以完成遍历操作即依次处理该数据结构的成员。的遍历某个数据结构过程是这样的比如对进行遍历创建一个指针对象,指向当前数组的起始位置。 Iterator 这真是毅种循环 Iterator不是array,也不是set,不是map, 它不是一个实体,而是一种访问机制,是一个用来访问某个对象的接口规范,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Ite...

    springDevBird 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<