【全面拥抱await,放弃.then】

一句话总结

时至今日,因.then造成的各种弊病痛点,前端构建项目应该全面拥抱await放弃.then的写法。

背景

曾经我也是遗老,坚持.then不动摇。我曾经坚信async await的语法糖会造成性能损失,一直拒绝使用。在callback回调地狱和.then的Promise地狱中挣扎过无数趟之后,现在我只感觉await真香。

三个历史阶段

前端控制异步代码,有三个历史阶段。

callback阶段

常规用法

const requestApi = (cb)=>{
// 模拟接口返回
setTimeout(function(){
const result = ‘异步接口返回的response’
cb(result);
},100)
}
const changeText = (text)=>{
document.querySelector(’#app’).innerHTML = text;
}
requestApi(changeText)

优点
性能优异。在异步调用极少的情况下,不需要引入Promise和async的包,所以性能较好。
兼容性好。最古老的JS底层也支持回调方式,无需引入Promise polfyfill
无构建型项目。不使用babel/TS编译器的项目可以直接使用这种方式
缺点
代码可维护性差
因js里的函数可以当参数传递,所以传入多少层都可以。这代表着当异步调用较多时会导致代码成了一坨缩进💩山
在这里插入图片描述
难以理解
当一个异步操作可能需要执行有不同的回调时,代码理解成本就以几何倍数增长起来了。

Promise then阶段

Promise对象自带.then和.catch函数来传入一个执行success和fail的回调。
可以有多个.then串在一起挨个执行,但如果.then里有异步操作,需要在.then里return 该Promise才能使得当前then链顺序执行。
常规用法

axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(function (response) {
  // 这里可以继续return Promise
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  })

优点
代码相对优雅。一定层度上解决了回调地狱的问题,面对较复杂的回调可以采用Promise的then链来解决
缺点

  1. 有一定的学习成本和理解成本,后期维护较长的Promise链较困难。
  2. Promise对异步的执行是依靠.then的链式调用实现的,但then传入的还是回调函数,决定了复杂的逻辑写在then里会越来越臃肿。
  3. 需要拆解为多个then进行长链式调用,这就注定了Promise在逻辑上没有完全解决异步优雅使用的问题。
  4. 在不同的异步调用链有一些异步调用中间链路重叠时,代码处理也会比较复杂。例如获取异步判断登陆,然后获取userInfo这样的例子。
async await阶段

async ES6入门教程
MDN async
H5与客户端交互的桥调用及Ajax都可以封装成一个返回Promise的函数。

export default JSApi(api, options ) => {
  return new Promise((resolve, reject) => {
    window.ucapi.invoke(api, {
      ...options,
      success: resolve,
      fail: reject
    })
  })
}	

async函数是把异步函数当同步用,await后面是一个Promise的对象,等待该Promise对象resolve后继续往后执行,实际上是Generator的yield,然后执行next()。

const changeText = (text)=>{
  document.querySelector('#app').innerHTML = text;
}
const requestApi = ()=>{
  return new Promise((resolve,reject)=>{
    // 模拟接口返回
    setTimeout(function(){
      const result = '异步接口返回的response'
      resolve(result);
    },1000)
  })
}
async function setDomText (){
  const text  = await requestApi();
  changeText(text)
}
setDomText()

优点

  • 异步完全同步化
  • 代码清晰,无大量的Promise Api,维护方便
  • 使用简单,理解成本低
  • 和函数最小作用原则一起使用,异步调用函数链路清晰

缺点

  • 错误处理需要有一定的经验
  • 需要一些polyfill来支持await语法糖 针对缺点1,在上面的ES6入门讲解里讲的非常清晰了。能正确的利用.catch,try catch和 if语句能很好的控制await的返回和错误处理。
    针对错误2,在支持Promise的环境,也只需要37行函数的兼容就可以了,其实性能损失并没有那么大,但带来的收益却非常大。

在这里插入图片描述
实例分析
抽取竞速游戏的一个较为典型的登陆获取用户信息案例进行分析,实际的代码比这要复杂的多。

export const loginThenGetUser = () => {
  return new Promise((resolve, reject) => {
    getUserInfo()
      // 用户已登录
      .then((args) => {
        resolve(args);
      })
      // 用户未登录
      .catch(() => {
        login()
          .then(() => {
            getUserInfo()
              .then((args) => {
                resolve(args);
              })
              .catch(() => {
                reject();
              });
          })
          .catch(() => {
            reject();
          });
      });
  });
};

可以看到,上面的函数已经地狱回调了。
修改为await如下

// 名字还可以再斟酌
const forceLoginAndGetUserInfo = async ()=>{
  const loginInfo = await login().catch(err=>{
    // 控制登陆失败的情况
  })
  if(loginInfo){
    // 登陆成功
    return await getUserInfo();
  }
}
const loginThenGetUser = async ()=>{
  let userInfo;
  try {
    userInfo = await getUserInfo()
  } catch (error) {
    // 移交另一个函数处理
    return forceLoginAndGetUserInfo()
  }
  // 已经登陆
  return userInfo
}

无论是login 还是getUserInfo 都是异步操作,封装成最小的异步调用函数,业务逻辑在调用时比.then清晰非常多,个人理解,说的不对地方还望大佬多多指教😄😄😄

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值