Promise: 异步编程的理解和使用

Promise 最早出现在 1988 年,由 Barbara Liskov、Liuba Shrira 首创(论文:Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems[1])。并且在语言 MultiLisp 和 Concurrent Prolog 中已经有了类似的实现。,图片,JavaScript 中,Promise 的流行是得益于 jQuery 的方法 jQuery.Deferred(),其他也有一些更精简独立的 Promise 库,例如:Q、When、Bluebird。,由于 jQuery 并没有严格按照规范来制定接口,促使了官方对 Promise 的实现标准进行了一系列重要的澄清,该实现规范被命名为 Promise/A+。后来 ES6(也叫 ES2015,2015 年 6 月正式发布)也在 Promise/A+ 的标准上官方实现了一个 Promise 接口。,想要实现一个 Promise,必须要遵循如下规则:,Promise 是一个提供符合标准的 then() 方法的对象。,初始状态是 pending,能够转换成 fulfilled 或 rejected 状态。,一旦 fulfilled 或 rejected 状态确定,再也不能转换成其他状态。,一旦状态确定,必须要返回一个值,并且这个值是不可修改的。,图片,ECMAScript’s Promise global is just one of many Promises/A+ implementations.,主流语言对于 Promise 的实现:Golang/promise、Python/promise、C#/Real-Serious-Games/c-sharp-promise、PHP/Guzzle Promises、Java/IOU、Objective-C/PromiseKit、Swift/FutureLib、Perl/stevan/promises-perl。,由于 JavaScript 是单线程事件驱动的编程语言,通过回调函数管理多个任务。在快速迭代的开发中,因为回调函数的滥用,很容易产生被人所诟病的回调地狱问题。Promise 的异步编程解决方案比回调函数更加合理,可读性更强。,传说中比较夸张的回调:,图片,现实业务中依赖关系比较强的回调:,实际上更真实的情况,往往是一个回调函数在多个文件间透传,要搞清楚最终在哪里触发需要翻越整个项目。,使用 Promise 梳理流程后:,若其中某个流程需要复用,单独把它抽离出来即可。,Promise 的运转实际上是一个观察者模式,then() 中的匿名函数充当观察者,Promise 实例充当被观察者。,图片,在 Promise 出现之前往往使用回调函数管理一些异步程序的状态。,图片,Promise 出现后使用 then() 接收事件的状态,且只会接收一次。,案例:插件初始化,工作中使用封装好的插件时,往往需要等待插件初始化成功后进行下一步操作。,使用回调函数:,插件代码:,使用 Promise:,插件代码:,相对于使用回调函数,逻辑更清晰,什么时候初始化完成和触发回调一目了然,不再需要重复判断状态和回调函数。当然更好的做法是只给使用方输出状态和数据,至于如何使用由使用方决定。,插件代码:,使用插件:,then() 必然返回一个 Promise 对象,Promise 对象又拥有一个 then() 方法,这正是 Promise 能够链式调用的原因。,由于返回一个 Promise 结构体永远返回的是链式调用的最后一个 then(),所以在处理封装好的 Promise 接口时没必要在外面再包一层 Promise。,Promise.all() / Promise.race() 可以将多个 Promise 实例包装成一个 Promise 实例,在处理并行的、没有依赖关系的请求时,能够节约大量的时间。,async&await 实际上只是建立在 Promise 之上的语法糖,让异步代码看上去更像同步代码,所以 async&await 在 JavaScript 线程中是非阻塞的,但在当前函数作用域内具备阻塞性质。,简洁干净,写更少的代码,不需要特地创建一个匿名函数,放入 then() 方法中等待一个响应。,处理条件语句,当一个异步返回值是另一段逻辑的判断条件,链式调用将随着层级的叠加变得更加复杂,很容易让人混淆。使用 async&await 将使代码可读性变得更好。,处理中间值,异步函数常常存在一些异步返回值,作用仅限于成为下一段逻辑的入场券,如果经历层层链式调用,很容易成为另一种形式的“回调地狱”。,对于多个异步返回中间值,搭配 Promise.all 使用能够提升逻辑性和性能。,靠谱的 await,await ‘str’ 等于 await Promise.resolve(‘str’),await 会把任何不是 Promise 的值包装成 Promise,看起来貌似没有什么用,但是在处理第三方接口的时候可以 “Hold” 住同步和异步返回值,否则对一个非 Promise 返回值使用 then() 链式调用则会报错。,await 阻塞 async 函数中的代码执行,在上下文关联性不强的代码中略显累赘。,Node.js 14+ 版本后,可以在 JavaScript module 中使用 await 操作符。在这之前,只能通过在 async 声明的场景中使用 await 操作符。,MDN 官方案例[2]:,1. 链式调用中尽量结尾跟 catch 捕获错误,而不是第二个匿名函数。因为规范里注明了若 then() 方法里面的参数不是函数则什么都不做,所以 catch(rejectionFn) 其实就是 then(null, rejectionFn) 的别名。,↑在以上代码中,anAsyncFn() 抛出来的错误 rejectError 会正常接住,但是 resolveSuccess 抛出来的错误将无法捕获,所以更好的做法是永远使用 catch。,若想错误管理精细一点,也可以通过 rejectError 来捕获 anAsyncFn() 的错误,catch 捕获 resolveSuccess 的错误。,2. 通过全局属性监听未被处理的 Promise 错误。,浏览器环境(window)的拒绝状态监听事件:,- unhandledrejection 当 Promise 被拒绝,并且没有提供拒绝处理程序时,触发该事件。,- rejectionhandled 当 Promise 被拒绝时,若拒绝处理程序被调用,触发该事件。,注意:Promise.reject() 和 new Promise((resolve, reject) => reject()) 这种方式不能直接触发 unhandledrejection 事件,必须是满足已经进行了 then() 链式调用的 Promise 对象才行。,当执行一个超级久的异步请求时,若超过了能够忍受的最大时长,往往需要取消此次请求,但是 Promise 并没有类似于 cancel() 的取消方法,想结束一个 Promise 只能通过 resolve 或 reject 来改变其状态,社区已经有了满足此需求的开源库 Speculation。,或者利用 Promise.race() 的机制来同时注入一个会超时的异步函数,但是 Promise.race() 结束后主程序其实还在 pending 中,占用的资源并没有释放。,若想按顺序执行一堆异步程序,可使用 reduce。每次遍历返回一个 Promise 对象,在下一轮 await 住从而依次执行。相同的场景,也可以使用递归实现,但是在 JavaScript 中随着数量增加,超出调用栈最大次数,便会报错。,图片,游戏联运业务中的登录模块,因为使用场景的复杂性,会有一个回调函数在各个文件间(此处指 Login.vue 和 State.js)传递。若想知道这个回调函数在哪里触发、传递了什么数据,需要逐层查找逻辑,并且需要进行类型判断。,图片,Login.vue,State.js,使用 Promise 改写后,简单调用时仅需要关注状态和值(userInfo),无需过度关注其在上游链路经历了什么。,图片,Login.vue,State.js,当前业务背景下,PC 游戏详情页常常充当手机游戏的宣传页面,来引导用户在手机端 App 下载对应游戏。在展示/进行推送之前需要确认几个前置条件,使用回调函数往往会产生冗余代码。,index.vue,使用 Promise 改写后,业务逻辑上便会更加清晰一点。,index.vue,[1]https://dl.acm.org/doi/10.1145/960116.54016,[2]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await,图片,钱程,游戏技术中台资深开发工程师,

文章版权声明

 1 原创文章作者:cmcc,如若转载,请注明出处: https://www.52hwl.com/18306.html

 2 温馨提示:软件侵权请联系469472785#qq.com(三天内删除相关链接)资源失效请留言反馈

 3 下载提示:如遇蓝奏云无法访问,请修改lanzous(把s修改成x)

 免责声明:本站为个人博客,所有软件信息均来自网络 修改版软件,加群广告提示为修改者自留,非本站信息,注意鉴别

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023年3月5日 上午12:00
下一篇 2023年3月7日 下午10:34