Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 335|回复: 0

关于js中异步问题的解决方案

[复制链接]
  • TA的每日心情
    奋斗
    3 小时前
  • 签到天数: 751 天

    [LV.10]以坛为家III

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    706360
    发表于 2021-4-21 10:22:21 | 显示全部楼层 |阅读模式

    在js中有一个始终无法绕过的问题,如何优雅地解决异步问题。实际上,js在执行过程中,每遇到一个异步函数,都会将这个异步函数放入一个异步队列中,只有当同步线程执行结束之后,才会开始执行异步队列中的函数,这个是讨论解决异步方案的前提。

    解决问题的方法

    主流的解决方法主要有以下几种:

    1. 回调函数
    2. 事件触发
    3. 发布/订阅者模式
    4. promise
    5. generate

    方法介绍

    回调函数

    回调函数应该属于最简单粗暴的一种方式,主要表现为在异步函数中将一个函数进行参数传入,当异步执行完成之后执行该函数

     
    回调函数

    这种写法最大的问题是:如果存在这样的一个业务场景,有三个异步函数A,B,C,其中B的执行需要在A执行结束之后,C的执行需要在B之后,这样的场景模拟成代码就是(jquery中ajax方法为例)
     
    回调函数地狱

    试想,如果再多几个异步函数,代码整体的维护性,可读性都变的极差,如果出了bug,修复的排查过程也变的极为困难,这个便是所谓的 回调函数地狱

     

    事件监听

    事件监听最常用的常见在于DOM元素事件绑定触发,如果我们想在DOM元素与用户进行鼠标或其他交互之后执行某些逻辑,就可以使用事件监听了


     
    事件监听

    引申开来,我们可以创建一个Event类,定义on和emit方法来绑定和触发自定义事件


     
    Event类

    同样回到在回调函数上遇到的难题,遇到多层嵌套的异步问题,使用事件触发的方法如何进行解决?
     
    事件监听

    嗯,从写法上来看,相比于回调函数,整洁了一些。

    发布/订阅者模式

    订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某发布者对象。这个发布者对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。vue就是基于发布/订阅者模式。
    如何通过代码实现一个发布/订阅者模式?
    引用了《Pro JavaScript Design Patterns》第15章的例子

    //创建一个主题发布类 var Publisher=function(){ this.subscribers=[] } Publisher.prototype.publish=function(data){ this.subscribers.forEach(function(fn){ fn(data) }) } /* 在Function上挂载这个些方法,所有的函数都可以调用这些方法 表示所有函数都可以订阅/取消订阅相关的主题发布 */ //订阅 Function.prototype.subscribe=function(publisher){ var that=this; var isExist=publisher.subscribers.some(function(el){ if(el===that){ return true } }) if(!isExist){ publisher.subscribers.push(that) } //return this是为了支持链式调用 return this } //取消订阅 Function.prototype.unsubscribe=function(publisher){ var that=this; //就是将函数从发布者的订阅者列表中进行删除 publisher.subscribers=publisher.subscribers.filter(function(el){ if(el!==that){ return true } }) return this } var publisher=new Publisher(); var subscriberObj=function(data){ console.log(data) } subscriberObj.subscribe(publisher) 

    这样就实现了一个简单的发布订阅者模式,每次发布者发布新内容时,就会调用publish方法,然后将内容作为参数,依次调用订阅者函数(subscribers)。
    其实,发布/订阅模式与事件监听很类似,

    • 事件监听是将一个回调函数事件绑定在一起,触发了相应事件,就会执行相应的回调函数
    • 发布/订阅模式是将订阅函数放入了发布者的订阅者列表中,更新时,遍历订阅者列表,执行所有的订阅者函数

    promise

    promise由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。


     
    阮大神对promise的描述

    简单来讲,就还是promise中有着三种状态pending,fulfilled,rejected。在代码中我们可以控制状态的变更

     
    Promise

    创建一个Promise对象需要传入一个函数,函数的参数是resolve和reject,在函数内部调用时,就分别代表状态由pending=>fulfilled(成功),pending=>rejected(失败)
    一旦promise状态发生变化之后,之后状态就不会再变了。比如:调用resolve之后,状态就变为fulfilled,之后再调用reject,状态也不会变化

    Promise对象在创建之后会立刻执行,因此一般的做法是使用一个函数进行包装,然后return一个promise对象

    function timeout(){ return new Promise(function(resolve,reject){ ...//异步操作 }) } 

    在使用时可以通过promise对象的内置方法then进行调用,then有两个函数参数,分别表示promise对象中调用resolve和reject时执行的函数

    function timeout(){ return new Promise(function(resolve,reject){ setTimeout(function(){ resolve(); },1000) }) } timeout() .then(function(){ ...//对应resolve时执行的逻辑 },function(){ ...//对应reject时执行的逻辑 }) 

    可以使用多个then来实现链式调用,then的函数参数中会默认返回promise对象

    timeout()
        .then(function(){ ...//对应resolve时执行的逻辑 },function(){ ...//对应reject时执行的逻辑 }) .then(function(){ ...//上一个then返回的promise对象对应resolve状态时执行的逻辑 },function(){ ...//上一个then返回的promise对象对应reject状态时执行的逻辑 }) 

    使用promise来解决回调地狱的做法就是使用then的链式调用

    function fnA(){ return new Promise(resolve=>{ ...//异步操作中resolve }) } function fnB(){ return new Promise(resolve=>{ ...//异步操作中resolve }) } function fnC(){ return new Promise(resolve=>{ ...//异步操作中resolve }) } fnA() .then(()=>{ return fnB() }) .then(()=>{ return fnC() }) 

    清晰直观了许多

    generate函数

    创建一个generate函数很简单

    function* gen(){ yield 1 yield 2 return 3 } 

    区别于普通函数的地方在于function后面的*号,以及函数内部的yield。
    *号是定义方式,带有 * 号表示是一个generate函数,yield是其内部独特的语法。

    function* gen(){ yield 1 yield 2 return 3 } let g=gen(); console.log(g.next())//{value:1,done:false} console.log(g.next())//{value:2,done:false} console.log(g.next())//{value:3,done:true} console.log(g.next())//{value:undefined,done:true} 

    调用generate函数会生成一个遍历器对象,不会立即执行,需要调用next执行,执行到带有yield的那一步,next会返回一个对象,对象中value表示yield或return后的值,done表示函数是否已经执行结束(是否已经执行到return)。之后每次执行next都会从上一个yield开始继续执行

    function* gen(){ let res=yield 1 yield res return 3 } let g=gen(); console.log(g.next())//{value:1,done:false} console.log(g.next(333))//{value:333,done:false} 

    在next中传入参数会作为上一次yield的返回值(会忽略第一个next中传递的参数)

    但是如何使用generate函数来进行异步编程?
    这里可以使用ES2017中的async和await语法(其实属于generate函数的语法糖)
    使用async替换*号,使用await替换yield就将generate函数改造成了一个async函数

    async function test(){ await 1 await asyncFn() } test() 

    其中await后面可以跟promise和原始数据类型(相当与同步操作),而async返回一个promise对象(因此可以使用then进行链式调用),使用时直接调用就行了

    我们再来看看如何解决回调地狱的问题

    function fnA(){ return new Promise(resolve=>{ ...//异步操作中resolve }) } function fnB(){ return new Promise(resolve=>{ ...//异步操作中resolve }) } function fnC(){ return new Promise(resolve=>{ ...//异步操作中resolve }) } async function gen(){ let resA=await fnA() let resB=await fnB(resA) let resC=await fnC(resB) } gen() 

    相比于之前的方法更简单直观,也更容易理解整体的代码流程。



    作者:醉里挑灯看剑_0696
    链接:https://www.jianshu.com/p/80dcd01d415e
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-6-20 23:10 , Processed in 0.062118 second(s), 30 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表