JS中如何优雅的使用async|JS中如何优雅的使用async await详解
目录
- jQuery的$.ajax
- Webpack时代的开始
- 深入了解Promise
- 消灭嵌套
- await-to-js
- 总结
jQuery的$.ajax
在开始之前我们先来聊聊我的js异步之路。在我还在学校的时候,那时候还是 jQuery 的天下,我直接接触到并且经常使用的异步操作就是网络请求,一手 $.ajax 走天下,伴我过了大二到毕业后差不多大半年的时间。
$.ajax( "/xxx" ).done(function() {// success !!! do something...}).fail(function() {// fail !!! do something...}).always(function() {// loading finished..});
不可否认,$.ajax 这个东西还是挺好使的,在面对大部分场景只有一个请求的情况下,完全胜任甚至觉得很棒
但是有个大大的问题,那就是面对请求链的时候就会特别特别的糟心,比如一个请求依赖于另一个请求的结果,两个可能还无所谓,要是五个八个的,可能想要直接自杀。。。
$.ajax('/xxx1').done(function() {// success !!! do something...$.ajax('/xxx2').done(function() {// success !!! do something...$.ajax('/xxx3').done(function() {// success !!! do something...$.ajax('/xxx4').done(function() {// success !!! do something...$.ajax('/xxx5').done(function() {// success !!! do something...// more...}).fail(function() {// fail !!! do something...}).always(function() {// loading finished..}); }).fail(function() {// fail !!! do something...}).always(function() {// loading finished..}); }).fail(function() {// fail !!! do something...$.ajax('/xxx6').done(function() {// success !!! do something...$.ajax('/xxx7').done(function() {// success !!! do something...// more....}).fail(function() {// fail !!! do something...}).always(function() {// loading finished..}); }).fail(function() {// fail !!! do something...}).always(function() {// loading finished..}); }).always(function() {// loading finished..}); }).fail(function() {// fail !!! do something...}).always(function() {// loading finished..}); }).fail(function() {// fail !!! do something...}).always(function() {// loading finished..});
抱歉,我不知道你可以套这么多层。。。,但事实就是TM经常出现这样的流程,大伙儿说说,这不能怪产品吧???只能怪自己学艺不精
像这样链式操作,我觉得吧,是个人可能都是奔溃的,先不说代码的可读性,就拿天天在变化的产品需求来说,也许先前是 请求1 结束之后紧接着 请求2 、 请求3 ,后面产品大手一挥,我觉得这个流程不大对,后面就变成了 请求2、 请求3 、 请求1,这尼玛套娃怎么改?可能有人会有疑问,为啥不用 axios 、 await 、async 呢?这个就不得不提项目代码是08年开写的JSP了。。。。在整了大半年的屎上拉屎以后,迎来了大大的转机,新写的项目开始往 Vue 上面转,并且放弃一部分兼容性,我TM直接起飞。。。
Webpack时代的开始
新的项目直接Vue + Webpack,我直接就给安排上 axios 、 await 、async ,现在代码非常好使,嵌套N层的代码没了
const r1 = await doSomthing1(); if (r1.xxx === 1) {const r2 = await doSomthing2(r1); const r3 = await doSomthing3(r2); // do something....} else {const r4 = await doSomthing4(r1); const r5 = await doSomthing5(r4); // do something....}// do something....
但是上面的代码存在一个问题,如果某个任务报错,那么代码直接就终止了。。。这样不符合我们的预期啊,那我们加上 try catch
let r1; try {r1 = await doSomthing1(); } catch (e) {// do something...return; }if (r1) {if (r1.xxx === 1) {let r2; try {r2 = await doSomthing2(r1); } catch (e) {// do something...return; }if (r2) {let r3; try {r3 = await doSomthing3(r2); } catch (e) {// do something...return; }// do something...}} else {let r4; try {r4 = await doSomthing4(r1); } catch (e) {// do something...return; }if (r4) {let r5; try {r5 = await doSomthing5(r4); } catch (e) {// do something...return; }}// do something...}// do something...}
???
优化了,等于没优化。。。
这时候我想聪明的小伙伴可能会说了,这是啥煎饼玩意儿。而呆滞的小伙伴已经开始想怎么解决这样的问题了。。。
深入了解Promise
我们来看一下 Promise 的定义
/** * Represents the completion of an asynchronous operation */interface Promise{/*** Attaches callbacks for the resolution and/or rejection of the Promise.* @param onfulfilled The callback to execute when the Promise is resolved.* @param onrejected The callback to execute when the Promise is rejected.* @returns A Promise for the completion of which ever callback is executed.*/then (onfulfilled?: ((value: T) => TResult1 | PromiseLike ) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike ) | undefined | null): Promise ; /*** Attaches a callback for only the rejection of the Promise.* @param onrejected The callback to execute when the Promise is rejected.* @returns A Promise for the completion of the callback.*/catch (onrejected?: ((reason: any) => TResult | PromiseLike ) | undefined | null): Promise ; }
【JS中如何优雅的使用async|JS中如何优雅的使用async await详解】then 和 catch 都会返回一个新的 Promise ,我相信很多小伙伴都已经想到了怎么解决方法,需要使用 try catch 是因为它会报错,那我们返回一个 永远不会报错的结果 不就行了?说干就干
消灭嵌套
function any(promise) {return promise.then((v) => v).catch((_) => null); }
这样就完全解决了啊???通过判断是否有值来判断是否成功,就不用再写 try catch 了,但是这样的代码有点不大好使,如果 then 返回的是一个 void 那么就完犊子了,一个 undefined 一个 null ,这还判断个锤子,我们再来改进一下
function any(promise) {return promise.then((v) => ({ ok: v, hasErr: false })).catch((e) => ({ err: e, hasErr: true })); }
使用的话
const r = await any(doSomething()); if (r.hasErr) {console.log(r.err); return; }console.log(r.ok);
现在看起来是不是很完美呢,赶紧和小伙伴推销一下。
小伙伴:???这啥煎饼玩意儿,不用不用。
我:这个我写的,在异步中用起来很好使的,告别嵌套try catch ,巴拉巴拉。。。
小伙伴:好的,下次一定用。
大家肯定有遇到过这样的情况,大家写的代码互相看不起,只要不是三方库,大家都是能不用同事写的就不用。。。
await-to-js
我都以为只有我一人欣赏,这一份优雅。事情出现转机,某天我正在刷github,发现了一个和我差不多异曲同工之妙的东西 await-to-js ,几行代码透露了和我一样的执着
// 下面是最新的代码/** * @param { Promise } promise * @param { Object= } errorExt - Additional Information you can pass to the err object * @return { Promise } */export function to(promise: Promise ,errorExt?: object): Promise {return promise.then((data: T) => [null, data]).catch((err: U) => {if (errorExt) {Object.assign(err, errorExt); }return [err, undefined]; }); }export default to;
再贴上使用示例
import to from 'await-to-js'; // If you use CommonJS (i.e NodeJS environment), it should be:// const to = require('await-to-js').default; async function asyncTaskWithCb(cb) {let err, user, savedTask, notification; [ err, user ] = await to(UserModel.findById(1)); if(!user) return cb('No user found'); [ err, savedTask ] = await to(TaskModel({userId: user.id, name: 'Demo Task'})); if(err) return cb('Error occurred while saving task'); if(user.notificationsEnabled) {[ err ] = await to(NotificationService.sendNotification(user.id, 'Task Created')); if(err) return cb('Error while sending notification'); }if(savedTask.assignedUser.id !== user.id) {[ err, notification ] = await to(NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you')); if(err) return cb('Error while sending notification'); }cb(null, savedTask); }async function asyncFunctionWithThrow() {const [err, user] = await to(UserModel.findById(1)); if (!user) throw new Error('User not found'); }
是不是感觉回来了,嵌套不再。。。
为了让小伙伴用上一行的代码,我只能忍痛推荐 await-to-js ,发上github地址,小伙伴:八百多star (ps: 现在2K+) 质量可靠,看了一下示例,嗯嗯,很不错,很完美,后面。。。后面的事不用我多说了,我自己写的也全换成了 await-to-js 。。。
我待世界如初恋,初恋却伤我千百遍
总结 我实现的版本其实存在着一点点问题的,在JS这样 灵活 的语言中,我改了返回值,别人就能直接抄我的家,类型不够严谨,要是放TS里,那就只能说一点小毛病,新加了 ok 、 err 、 hasErr 增加了一小点点case,但并不致命
await-to-js 中一点点的设计哲学,为啥把错误放在数组的第一个位置,而不是把成功放在第一个位置,就很明示:永远谨记错误,把错误放在第一位,而不是很 自信 成功,就忘记错误的惨痛。
const [, result] = await to(iWillSucceed());
参考资料
- $.ajax
- Promise
- await-to-js
推荐阅读
- 热闹中的孤独
- Shell-Bash变量与运算符
- JS中的各种宽高度定义及其应用
- 2021-02-17|2021-02-17 小儿按摩膻中穴-舒缓咳嗽
- 深入理解Go之generate
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- 异地恋中,逐渐适应一个人到底意味着什么()
- 如何寻找情感问答App的分析切入点
- 我眼中的佛系经纪人
- 《魔法科高中的劣等生》第26卷(Invasion篇)发售