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 相关文章推荐
js静态作用域的功能。
Dec 25 Javascript
不得不分享的JavaScript常用方法函数集(上)
Dec 23 Javascript
jQuery遍历DOM元素与节点方法详解
Apr 14 Javascript
关于angularJs清除浏览器缓存的方法
Nov 28 Javascript
使用axios实现上传图片进度条功能
Dec 21 Javascript
jQuery实现的回车触发按钮事件功能示例
Mar 25 jQuery
Node.js文件编码格式的转换的方法
Apr 27 Javascript
浅谈node中的cluster集群
Jun 02 Javascript
微信小程序框架wepy之动态控制类名
Sep 14 Javascript
vue+element项目中过滤输入框特殊字符小结
Aug 07 Javascript
vue实现的多页面项目如何优化打包的步骤详解
Jul 19 Javascript
交互式可视化js库gojs使用介绍及技巧
Feb 18 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
php下使用SMTP发邮件的代码
2008/01/10 PHP
phpMyAdmin 链接表的附加功能尚未激活问题的解决方法(已测)
2012/03/27 PHP
基于HBase Thrift接口的一些使用问题及相关注意事项的详解
2013/06/03 PHP
PHP实现简易blog的制作
2016/10/24 PHP
javascript实现的动态文字变换
2007/07/28 Javascript
页面中body onload 和 window.onload 冲突的问题的解决
2009/07/01 Javascript
Jquery Ajax学习实例7 Ajax所有过程事件分析示例
2010/03/23 Javascript
js中的cookie的读写操作示例详解
2014/04/17 Javascript
js读写json文件实例代码
2014/10/21 Javascript
JavaScript跨浏览器获取页面中相同class节点的方法
2015/03/03 Javascript
angular2使用简单介绍
2016/03/01 Javascript
JS面试题---关于算法台阶的问题
2016/07/26 Javascript
javascript表达式和运算符详解
2017/02/07 Javascript
基于JavaScript实现瀑布流效果
2017/03/29 Javascript
js封装成插件的步骤方法
2017/09/11 Javascript
使用vue官方提供的模板vue-cli搭建一个helloWorld案例分析
2018/01/16 Javascript
vue中倒计时组件的实例代码
2018/07/06 Javascript
vue项目使用微信公众号支付总结及遇到的坑
2018/10/23 Javascript
详解Puppeteer前端自动化测试实践
2019/02/21 Javascript
vue实现带复选框的树形菜单
2019/05/27 Javascript
JS实现盒子拖拽效果
2020/02/06 Javascript
[03:16]DOTA2完美大师赛主赛事首日集锦
2017/11/23 DOTA
简单谈谈python中的语句和语法
2017/08/10 Python
对Python中gensim库word2vec的使用详解
2018/05/08 Python
Python数据类型之Tuple元组实例详解
2019/05/08 Python
深入了解Python枚举类型的相关知识
2019/07/09 Python
python实现拼图小游戏
2020/02/22 Python
python 实现分组求和与分组累加求和代码
2020/05/18 Python
Python实现数字的格式化输出
2020/08/01 Python
HTML5中的Web Notification桌面右下角通知功能的实现
2018/04/19 HTML / CSS
皇家阿尔伯特瓷器美国官网:Royal Albert美国
2020/02/16 全球购物
市优秀教师事迹材料
2014/02/05 职场文书
春节联欢会策划方案
2014/05/16 职场文书
2014年教师思想工作总结
2014/12/03 职场文书
python numpy中setdiff1d的用法说明
2021/04/22 Python
HTML中实现音乐或视频自动播放案例详解
2022/05/30 HTML / CSS