博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
切图崽的自我修养-[ES6] 异步函数管理方案浅析
阅读量:7056 次
发布时间:2019-06-28

本文共 5586 字,大约阅读时间需要 18 分钟。

前言

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

优缺点:

优点:

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

缺点:

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

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

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


解决方案

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

  • 原生的回调函数

  • 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{}块,开始执行这个代码片段

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

  2. 如果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机制,错误处理非常非常容易.

结言

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

转载地址:http://dglol.baihongyu.com/

你可能感兴趣的文章
memcached 在生产环境下安装脚本
查看>>
解析 WEB 中所有 URL 的简单牛B代码,先保存起来,方式将来找不到了
查看>>
Linux系统管理(二):Linux系统启动流程
查看>>
puppet安装配置
查看>>
CUDA学习(三十五)
查看>>
实战部署MySQL用户认证的Postfix邮件系统(3)
查看>>
Spring(11)——可用来修改bean对象的BeanPostProcessor
查看>>
网络故障排除连载之三:广域网串行链路故障排除
查看>>
十二种常用的密码破解法
查看>>
grep man选项
查看>>
SSRS 2012 聚合函数 -- 指定分页示例
查看>>
什么是latch以及如何导致latch争用
查看>>
Hyper-V 2016 系列教程54 SCVMM 2016 布署前 Windows评估和部署工具包 Windows ADK安装
查看>>
linux之强制位及冒险位
查看>>
debug
查看>>
如何关闭Microsoft OutLook 2013 Search功能
查看>>
Android倒计时 Android仿京东倒计时 android电商app源码倒计时源码
查看>>
教你一个查找下载缓存文件的自带方法
查看>>
单点故障的解决方案
查看>>
SQL Server 默认跟踪应用2 -- 审核备份和恢复事件
查看>>