JS中如何优雅的使用async await详解


Posted in Javascript onOctober 05, 2021

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<T> {
    /**
     * 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<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;

    /**
     * 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<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}

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<T, U = Error> (
  promise: Promise<T>,
  errorExt?: object
): Promise<[U, undefined] | [null, T]> {
  return promise
    .then<[null, T]>((data: T) => [null, data])
    .catch<[U, undefined]>((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());

参考资料

到此这篇关于JS中如何优雅的使用async await的文章就介绍到这了,更多相关JS优雅使用async await内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
向fckeditor编辑器插入指定代码的方法
May 25 Javascript
动态样式类封装JS代码
Sep 02 Javascript
jQuery中scrollLeft()方法用法实例
Jan 16 Javascript
jQuery实现左右切换焦点图
Apr 03 Javascript
超漂亮的jQuery图片轮播特效
Nov 24 Javascript
基于JS实现的笛卡尔乘积之商品发布
May 13 Javascript
ECMAScript6 新特性范例大全
Mar 24 Javascript
LayerClose弹窗关闭刷新方法
Aug 17 Javascript
Vue项目pdf(base64)转图片遇到的问题及解决方法
Oct 19 Javascript
微信小程序上线发布流程图文详解
May 06 Javascript
基于Vue CSR的微前端实现方案实践
May 27 Javascript
关于angular浏览器兼容性问题的解决方案
Jul 26 Javascript
js中Object.create实例用法详解
Oct 05 #Javascript
TypeScript中条件类型精读与实践记录
Oct 05 #Javascript
SSM VUE Axios详解
Ajax实现三级联动效果
Oct 05 #Javascript
5种 JavaScript 方式实现数组扁平化
Oct 05 #Javascript
国庆节到了,利用JS实现一个生成国庆风头像的小工具 详解实现过程
Oct 05 #Javascript
Javascript设计模式之原型模式详细
You might like
雄兵连三大错觉:凯莎没了,凉冰阵亡了,华烨觉得自己又行了
2020/04/09 国漫
php 设计模式之 工厂模式
2008/12/19 PHP
解析php获取字符串的编码格式的方法(函数)
2013/06/21 PHP
php实现的中文分词类完整实例
2017/02/06 PHP
Laravel 集成微信用户登录和绑定的实现
2019/12/27 PHP
跟着JQuery API学Jquery 之三 筛选
2010/04/09 Javascript
鼠标选择动态改变网页背景颜色的JS代码
2013/12/10 Javascript
javascript操作excel生成报表全攻略
2014/05/04 Javascript
Perl Substr()函数及函数的应用
2015/12/16 Javascript
JS获取地址栏参数的两种方法(简单实用)
2016/06/14 Javascript
jQuery实现的表头固定效果实例【附完整demo源码下载】
2016/08/01 Javascript
纯js实现悬浮按钮组件
2016/12/17 Javascript
Vue2路由动画效果的实现代码
2017/07/10 Javascript
微信小程序wx.uploadfile 本地文件转base64的实现代码
2018/06/28 Javascript
CSS3 动画卡顿性能优化的完美解决方案
2018/09/20 Javascript
说说如何使用Vuex进行状态管理(小结)
2019/04/14 Javascript
详解vue中使用protobuf踩坑记
2019/05/07 Javascript
Layui table field初始化加载时进行隐藏的方法
2019/09/19 Javascript
利用原生JS实现欢乐水果机小游戏
2020/04/23 Javascript
python3.5实现socket通讯示例(TCP)
2017/02/07 Python
Python编程实现删除VC临时文件及Debug目录的方法
2017/03/22 Python
Python中字典(dict)合并的四种方法总结
2017/08/10 Python
python中map()函数的使用方法示例
2017/09/29 Python
Python 数据可视化pyecharts的使用详解
2019/06/26 Python
利用python numpy+matplotlib绘制股票k线图的方法
2019/06/26 Python
Django框架模型简单介绍与使用分析
2019/07/18 Python
在vscode中配置python环境过程解析
2019/09/28 Python
Expedia印度:您的一站式在线旅游网站
2017/08/24 全球购物
ECCO爱步官方旗舰店:丹麦鞋履品牌
2018/01/02 全球购物
YesBabyOnline美国:全球性的在线婚纱礼服工厂
2018/05/05 全球购物
纽约州一群才华横溢的金匠制作而成:Hearth Jewelry
2019/03/22 全球购物
环境科学专业个人求职的自我评价
2013/11/28 职场文书
中专生自我鉴定范文
2013/12/19 职场文书
优秀中学生事迹材料
2014/01/31 职场文书
班级文化建设标语
2014/06/23 职场文书
Redis配置外网可访问(redis远程连接不上)的方法
2022/12/24 Redis