在我最近的开发中,遇到了下面几个场景:
乍一看,它们都是基于业务遇见的不一样的应用场景。然而,在实际开发中,他们都和Promise异步编程有着不可分割的关系。在真正解决了上面的问题之后,不难发现,解决方案其实都是基于一些基础的Promise知识来进行二次拓展的。
为了能够加深对Promise及其常用静态方法的理解,在日后遇到相似问题时提高解决效率,我总结了这篇文章。希望在能给到自己和更多的同学一些知识积累和启发。
我第一次遇到这个问题是在一个需求中,我们需要实现如下效果:
点击“进行中”Tab,显示Loading状态
前端同时拉取 “答题中” 和 “未考”的两个考试接口。结果都都返回后,把“答题中”的数据放在最顶。
取消Loading状态,展示列表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aUc01YRM-1654666968156)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a3178bd3a2e841d7839cc7c1aad42338~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]
当组件的渲染需要多个接口的数据时,因为接口的返回时长往往不一致,用同步代码来发送请求会导致问题。问题往往是部分接口先得到返回结果,并进行渲染,其他接口后得到返回结果再次更新视图,导致页面有“闪屏”的情况,用Promise.all()可以很好地避免此类问题:

但值得注意的是,使用Promise.all()需要等待所有传入的Promise的状态都变为Fulfilled,或者其中一个Promise的状态变为Rejected时,代码才能进入到Promise.all的回调方法中去。此时页面的渲染时间为所有接口返回时间的最大值。
拓展: 还有另外一个值的考虑的问题是:一旦有传入的Promise状态变为Rejected了,且没有自己的catch方法,Promise.all就会进入catch回调。这使得有时候仅仅只是一个无关紧要的接口挂掉了,但因为我们使用了Promise.all(),导致整个页面都无法渲染出来。通过给每个传入的Promise增加catch方法,我们可以做到把多个请求合并在一起,哪怕有的请求失败了,也返回给我们,我们只需要在一个地方处理这些数据和错误的逻辑即可。


经过上述改动后, 传入的Promise在请求接口失败会首先会把我们自定义的错误信息Rejected出去。而我们给传入的Promise定义了自己的catch方法,该方法会返回一个新的Promise实例,在执行完catch方法后,也会变成Resolved。这样,Promise.all()方法参数里面的两个实例都会Resolved,因此我们可以在then方法中统一处理数据与错误的逻辑。
经过上述的处理后,使用Promise.all()时,就再也不用担心因为一个请求失败导致整个页面都无法渲染数据了。
补充: 实际上, 为了解决上述提到的Promise.all()在使用中的问题,ES2020 引入了Promise.allSettled()方法,用来确定一组异步操作是否都结束了。相较于Promise.all(),它最大的优点是:无论参数实例Resolve还是Reject,Promise.allSettled()都会执行then方法的第一个回调函数,而不会catch到参数实例的Rejected状态。

同时,感谢各位同学提醒,axios本身返回Promise,无需在外部进行进行额外的Promise包装。但目前Promise.allSettled()依然处于TC39第4阶段草案,使用时仍需注意兼容性的问题,附上兼容性对比图:
Promise.allSettled()(数据来源于Promise.allSettled() - MDN, 2021-12-08)
Promise.all()(数据来源于Promise.all() - MDN, 2021-12-08)
我们在上面已经说明了如何改进Promise.all()的使用方法,使得我们可以在Promise.all().then()回调中同时处理接口拉取的数据以及相关错误逻辑。
然而,我们会发现,当我们需要同时拉取的接口数越来越多时,Promise.all().then()回调中需要写的逻辑也就会越来越臃肿。即使上面仅仅是处理两个简单的接口,其if/else逻辑就已经给我们造成了很大的阅读障碍了。然而,这类问题实际上不仅仅只有Promise.all()才会引起的,当一个接口返回了大量我们需要做不同处理的数据时(比如把上面的两个或多个接口合并为一个),我们也需要考虑如何才能优化此处代码的可阅读性。
实际上,我们可以通过灵活地运用Promise.prototype.then(),来实现一个中间件功能,以分割不同的数据处理逻辑:

通过用Promise.prototype.then()作为中间件,我们的代码能够清晰地分成不同的逻辑处理区块。甚至,我们还可以把不同的逻辑处理封装成函数。这样,整个函数的逻辑会更方便自己和其他后续的维护者去阅读。
为了给予用户足够好的体验,在项目的特定场景中,我们会做一些接口超时重试的操作。比如用户正在地铁上浏览一门课程,该课程需要记录用户的学习时长。当网络短暂不佳的时候,我们就需要重试机制,来在用户无感知的情况下重新请求学习时长记录接口,来避免出现网络短暂不可用导致用户学习无法记录的情况。
当然,这种超时重试的机制应该在各大AJAX库都有实现。 但在我们使用fetch等原生的Web API时,无法掌握接口重试的实现就会有点麻烦了。实际上,Promise.race()的灵活使用能够让我们轻松实现一个接口重试逻辑:

上述代码中,我定义了一个timeout函数与一个请求request函数。同时定义了一个ajax函数,用于传入请求url,超时时间以及重试次数。如果request的接口在超时时间内依然没有返回,Promise.race()将会被我们定义的timeout函数Rejected掉。通过上述场景1和场景2说的方法,给传入的Promise定义catch函数,我们可以在同一个地方判断Promise.race中最终Resolved的原因,来执行请求成功返回/超时对应的逻辑。
加载图片的最大并发数为maxNum
每当有一张图片加载成功/失败,就腾出一个空位,可以加载剩余未加载的图片
所有图片加载完成后,结果按照加载顺序依次打出
这个场景来自 Chris Jensen , 基于他写的一个有趣的Promise.race()方法用例, 我也试着来模拟批量加载图片:

与Promise.all()相反,Promise.any() 接收一个Promise可迭代对象,只要其中的一个Promise成功,就返回那个已经成功的 promise。因此,我们可以利用这个特点来获取第一张成功加载的图片。如一些需要随机显示一张图片的场景,就可以一次性加载多张,再利用Promise.any()获取最快记载好的图片,以提高首屏渲染速度。再如你有多台服务器,则可以使用Promise.any()从响应速度最快的服务器检索需要的资源。
注意! Promise.any() 方法依然是实验性的,尚未被所有的浏览器完全支持。
1. Promise.race():
Promise的场景,搭配一个定时器传入Promise.race()方法,做到超时打断/重试的效果promises数组,用race()方法及时处理最快掉resolved的Promise。为后续等待队列中的任务腾出位置2. Promise.all()
Promise参数定义catch函数,能够方便我们在同一个地方处理数据渲染/接口错误逻辑,避免因为一个小接口导致整个页面都无法渲染出来的情况。3. Promise.prototype.then()
then()作为中间件,用于分割不同的代码逻辑。能改避免在回调函数中,业务逻辑过于臃肿导致难以维护的情况。then()的链式调用同时适用于简化接口之间相互依赖的情况,但async/await无疑在大部分情况下更胜一筹。4. Promise.any()
对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r
刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr
我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R
是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty