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

前言

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

优缺点:

优点:

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

缺点:

  • 异步函数的书写方式和代码执行逻辑很不直观,回调函数这种方式不太符合人类的的线性思维
  • 异步函数的执行流程通常不好管理
  • 不好对异步函数部署错误处理机制

解决方案

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

  • 原生的回调函数
  • 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机制,错误处理非常非常容易.

结言

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

原文地址:https://www.cnblogs.com/jlfw/p/12614532.html

时间: 2024-08-04 23:02:22

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

切图崽的自我修养-优化图片加载流程

前言 优化! 又是优化! 切图崽们作为整个web应用的纽带,连接着用户行为和机器性能. 而优化的最终意义,在于在这两者之间取得一个最佳的平衡点. 对于图片资源的加载来说,更是如此. 今天我们就来简单说说,项目开发中常见的图片加载优化方式. 预加载 1.遮罩大法 我们经常用jquery, jquery中$(function){})实际上是DOMContentLoaded事件完成的回调,只是完成了DOM树的构建. 诸如Css的渲染以及页面内图片等资源的下载不一定完成了.所以如果此时呈现页面,页面会非

切图崽的自我修养-[MVVM] Js MV*模式浅谈

前言 做客户端开发.前端开发对MVC.MVP.MVVM这些名词不了解也应该大致听过,都是为了解决图形界面应用程序复杂性管理问题而产生的应用架构模式.网上很多文章关于这方面的讨论比较杂乱,各种MV模式之间的区别分不清,甚至有些描述都是错误的.本文追根溯源,从最经典的Smalltalk-80 MVC模式开始逐步还原图形界面之下最真实的MV模式. GUI程序所面临的问题 图形界面的应用程序提供给用户可视化的操作界面,这个界面提供给数据和信息.用户输入行为(键盘,鼠标等)会执行一些应用逻辑,应用逻辑(a

一个前端的自我修养

①一个前端的自我修养 今天给大家分享的主题是前端的自我成长,这是一个关于成长的话题. 很多人都有这样的感觉:听了很多技术圈子的分享,有的有深度,有的循循善诱,深入浅出,但是呢,几年下来,到底哪些用上了,哪些对自己真的有帮助了?反而有些模糊. 2015 年我在不同的场合分享了很多内容:有移动端的性能.有适配.有 Web vs Native,也有 hybrid,但是其实我一直比较担心,真正有深度的内容,其实面向的是比较小众的群体,比如说 Hybrid,其实它在大部分公司里面,是只能用现成的. 所以我

程序员的自我修养(2)——计算机网络(转)

相关文章:程序员的自我修养——操作系统篇 几乎所有的计算机程序,都会牵涉到网络通信.因此,了解计算机基础网络知识,对每一个程序员来说都是异常重要的. 本文在介绍一些基础网络知识的同时,给出了一些高质量的系列文章链接,以方便大家随时参考学习.相信通过本文的学习,你能对计算机网络有全面的认识! 在阅读本文之前,建议阅读以下两遍文章,以便对”计算机网络是如何工作”的有个大概的了解. 互联网协议入门(一) 互联网协议入门(二) 接下来,我们介绍一些基础网络知识. OSI参考模型 一上来就是OSI七层参考

程序员的自我修养 学习笔记(1)

本文源自在学习<程序员的自我修养>中的心得体会. 对于底层系统程序开发者来说,硬件平台可以抽象为三个主要部件,CPU.内存.I/O控制器. 早期的计算机没有复杂的图形功能,CPU和内存之间的频率差异不大,它们都是连接在同一个bus上面的.其他I/O设备,诸如显示设备.键盘.磁盘等速度比内存.CPU慢很多.为了IO设备与CPU.内存之间的协调通讯,一般每个IO设备商都有相应的IO控制器,早期的硬件结构图如下: 随着技术的进步,CPU的频率越来越高,内存跟不上CPU的速度,他们之间就需要一个转换机

程序员的自我修养

本书主要介绍系统软件的运行机制和原理,涉及在Windows和Linux两个系统平台上,一个应用程序在编译.链接和运行时刻所发生的各种事项,包括:代码指令是如何保存的,库文件如何与应用程序代码静态链接,应用程序如何被装载到内存中并开始运行,动态链接如何实现,C/C++运行库的工作原理,以及操作系统提供的系统服务是如何被调用的.每个技术专题都配备了大量图.表和代码实例,力求将复杂的机制以简洁的形式表达出来.本书最后还提供了一个小巧且跨平台的C/C++运行库MiniCRT,综合展示了与运行库相关的各种

GIS制图人员的自我修养(2)--制图意识

by 李远祥 上次提及到GIS制图人员的一些制图误区,主要是为GIS制图人员剖析在制图工作中的一些问题.但如何提高制图的自我修养,却是一个非常漫长的过程,这一章主要为提升制图修养作一些理论铺垫.其中,最值得强调的就是制图意识. 什么是制图意识?就是当第一时间看到数据的时候,就立刻针对该数据做出反应,基本上形成初步的制图思路,并确定制图的技术路线.说到底,所谓的制图意识,对于制图界的老鸟来说,就是经验.但是经验往往是需要长时间的积累的.还有一种情况就是具有制图天赋的人,天生就具备这种敏锐的触觉.但

论一个程序员的自我修养

在<喜剧之王>中,周星驰扮演的尹天仇,一直梦想成为一名演员,而他不管是在扮演跑龙套,或者在街坊中开设演员训练班,亦或成为主角时,他对待演员的态度,始终是认真,热爱而又投入的.而那一本他随身携带的书--<演员的自我修养>,尽管不知道里面具体写的是什么,但我猜,他对待演员的态度和行为,就是书中内容显示的. 于是,不禁问了问自己,作为一名程序员,一个“程序员的自我修养”是什么? 尽管我们不一定要像尹天仇那么的认真对待自己的事业,但,一些基本的修养,作为一名新时代的码农,总应该是要具备的吧

操作系统的自我修养-04 从U盘启动MerxOs操作系统

转载注意出处:K_Linux_Man 我们一直都是使用bochs模拟器来启动我们的MerxOs操作系统,模拟真的不好玩,我们今天就让我们的"MerxOs:Hello,World" 运行在真机上.现代化的今天, U盘早已经替代软盘,所以我们以U盘为例,讲述如何让我们的MerxOS操作系统从U盘加载. U盘的第一扇区 U盘的第一个扇区由三部分组成: 第 1 部分(0x0000~0x01BD)446 个字节为MBR,MBR(Master Boot Record)主引导扇区,我们的MerxOs