JS中优雅的使用async await

大家好,我是 漫步,今天来分享一篇关于await灵活应用的文章。喜欢记得关注我并设为星标。

jQuery的$.ajax

在开始之前我们先来聊聊我的js异步之路。在我还在学校的时候,那时候还是 jQuery 的天下,我直接接触到并且经常使用的异步操作就是网络请求,一手 $.ajax[1] 走天下,伴我过了大二到毕业后差不多大半年的时间。

$.ajax( "/xxx" )
  .done(function() {
    // success !!! do something...
  })
  .fail(function() {
    // fail !!! do something...
  })
  .always(function() {
    // loading finished..
  });

不可否认,$.ajax 这个东西还是挺好使的,在面对大部分场景只有一个请求的情况下,完全胜任甚至觉得很棒116e3ccd8c225ae4ee390b2da0b2aa61.png但是有个大大的问题,那就是面对请求链的时候就会特别特别的糟心,比如一个请求依赖于另一个请求的结果,两个可能还无所谓,要是五个八个的,可能想要直接自杀。。。

$.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..
  });

f0b50e3e56892e121154bbaa912f2372.png抱歉,我不知道你可以套这么多层。。。,但事实就是TM经常出现这样的流程,大伙儿说说,这不能怪产品吧???只能怪自己学艺不精f68187ccf79311e4e24da099aa75880f.png  像这样链式操作,我觉得吧,是个人可能都是奔溃的,先不说代码的可读性,就拿天天在变化的产品需求来说,也许先前是 请求1 结束之后紧接着 请求2请求3 ,后面产品大手一挥,我觉得这个流程不大对,后面就变成了 请求2请求3请求1,这尼玛套娃怎么改?可能有人会有疑问,为啥不用 axiosawaitasync 呢?这个就不得不提项目代码是08年开写的JSP了。。。。在整了大半年的屎上拉屎以后,迎来了大大的转机,新写的项目开始往 Vue 上面转,并且放弃一部分兼容性,我TM直接起飞。。。

Webpack时代的开始

新的项目直接Vue + Webpack,我直接就给安排上 axiosawaitasync ,现在代码非常好使,嵌套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...
}

???92d64344f5e96375552617b14103ef1f.png优化了,等于没优化。。。df73f7cd519c58940afc8dfa7c501a91.png这时候我想聪明的小伙伴可能会说了,这是啥煎饼玩意儿。而呆滞的小伙伴已经开始想怎么解决这样的问题了。。。

深入了解Promise

我们来看一下 Promise[2] 的定义

/**
 * 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>;
}

thencatch 都会返回一个新的 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 ,巴拉巴拉。。。小伙伴:好的,下次一定用。1fe90cda35a0c59aede7658efb11db00.png大家肯定有遇到过这样的情况,大家写的代码互相看不起,只要不是三方库,大家都是能不用同事写的就不用。。。fd86b6ca4379588c20f2f5a4c13de18c.png

await-to-js

我都以为只有我一人欣赏,这一份优雅。事情出现转机,某天我正在刷github,发现了一个和我差不多异曲同工之妙的东西 await-to-js[3] ,几行代码透露了和我一样的执着

// 下面是最新的代码
/**
 * @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[4] ,发上github地址,小伙伴:八百多star (ps: 现在2K+) 质量可靠,看了一下示例,嗯嗯,很不错,很完美,后面。。。后面的事不用我多说了,我自己写的也全换成了 await-to-js[5] 。。。4acc7dfc308593b07be7864919ac4a20.png我待世界如初恋,初恋却伤我千百遍

总结

  • 我实现的版本其实存在着一点点问题的,在JS这样 灵活 的语言中,我改了返回值,别人就能直接抄我的家,类型不够严谨,要是放TS里,那就只能说一点小毛病,新加了 okerrhasErr 增加了一小点点case,但并不致命

    • await-to-js[6] 中一点点的设计哲学,为啥把错误放在数组的第一个位置,而不是把成功放在第一个位置,就很明示:永远谨记错误,把错误放在第一位,而不是很 自信 成功,就忘记错误的惨痛。

const [, result] = await to(iWillSucceed());

如果文章对您有帮助的话,欢迎 点赞 、 评论 、 收藏 、 分享 ,您的支持是我码字的动力,万分感谢!!!🌈18e375cabfb95334621753d87a73385d.png

参考资料

  • $.ajax[7]

  • Promise[8]

  • await-to-js[9]

关于本文

作者:尽管如此世界依然美丽
https://juejin.cn/post/7014749066005331975

参考资料

[1]

https://api.jquery.com/jQuery.ajax/

[2]

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

[3]

https://github.com/scopsy/await-to-js

- EOF -

相关文章  点击标题可跳转

异步编程的终极解决方案 async/await

手写async await的最简实现(20行搞定)

await 只在 async 函数中工作

关注下方「前端热榜」,回复 “思维图”

获取公众号所有JS思维图

我是漫步,分享技术,不止前端,下期见~

最后,欢迎加我的微信,拉你进上百人的前端交流群

f79df33ceb7bcfcfda6e8b6ba760c6f8.png

创作不易,加个点赞、在看 支持一下哦!9d84ca537b2f81923c2b5c6d25394e25.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值