前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端异步代码解决方案实践(一)

前端异步代码解决方案实践(一)

作者头像
前朝楚水
发布2018-07-26 10:51:14
1.3K0
发布2018-07-26 10:51:14
举报
文章被收录于专栏:互联网杂技互联网杂技

小程序框架提供丰富的原生API,可以方便调起微信提供的能力,如获取用户信息,本地存储,支付功能等。但大多数API为异步调用,需要传递成功或失败回调函数,例如wx.request发起https请求需要在成功或失败回调中书写业务逻辑,这时就很容易会遇到回调地狱问题。另外一方面,错误或异常处理会和业务代码写在一起,代码耦合高。

具体业务场景示例,小程序登录流程需要先去调用微信登录接口wx.login获取code值,登录成功后再去调用获取用户信息接口wx.getUserInfo获取用户相关信息,拿到codeuserInfo后调用业务登录接口换取登录信息。在这种多层异步嵌套场景中,假如使用回调处理逻辑,就会写出一层嵌一层的代码,例如下面伪代码:

// 登录流程
wxLogin((res) => {
  let code = res.code

  // 登录后获取用户信息
  wxGetUserInfo((res) => {
    let someUserData = res
    let data = {
      code,
      ...someUserData
    }

    // 请求业务接口,换取登录信息
    requestDjLogin(data, (res) => {
      // 保存登录信息
      // 业务处理...
    }, (err) => {
      // 异常处理 
    })
  }, (err) => {
      // 异常处理
  })
}, (err) => {
  // 异常处理
})

如上所示,采用回调函数解决异步问题。三个异步操作两个回调,代码已经开始变得不方便维护。或许三层异步操作还没有达到忍无可忍极限,但如果业务场景需要五层嵌套或更多情况下,就需要采用新的方式书写异步代码。

Promise对象

ES6中提出Promise对象语法。Promise对象是一个代理对象,允许为异步代码执行结果的成功和失败分别绑定相应的处理方法

语法

new Promise((resolve, reject) => {
  // 异步操作
})

Promise有以下几种状态:

  • pending: 初始状态,未履行或拒绝
  • fulfilled: 意味着操作成功完成
  • rejected: 意味着操作失败

pending 状态的 Promise 对象可能以 fulfilled 状态返回了一个值,也可能reject返回一个值。then方法包含两个函数类型参数:onfulfilled 和 onrejected。当fulfilled状态时,调用 then 的 onfulfilled 方法,当Promise被拒绝时,调用 then 的 onrejected 方法。同时then方法返回 promise对象自身支持链式调用。

结合promise语法我们可以将上面的代码修改为更容易维护的代码。将上面三个异步操作封装为promise对象。

// 微信登录API promise封装
function wxLogin () {
  return new Promise((resolve, reject) => {
    wx.login({
      success: (res) => resolve(res),
      fail: (err) => reject(err)
    })
  })
}

// 微信获取用户信息API 封装
function wxGetUserInfo () {
  // return new Promise ...
}

// 请求业务登录接口封装
function requestDjLogin () {
  // return new Promise ...
}

// 登录流程代码
let code = null
let someUserData = null

// 获取微信登录code
wxLogin().then((res) => {
  code = res.code

  // 微信登录成功后,
  // 返回获取用户信息 promise对象
  return wxGetUserInfo()
}).then((res) => {
  someUserData  = res
  let data = {
     code,
     ...someUserData
  }

  // 获取用户信息成功后,
  // 返回请求业务登录接口 promise对象
  return requestDjLogin(data)
}).then((res) => {
  // 保存登录信息
}).catch((err) => {
  // 异常处理
  console.log(`err = ${err}`)
})

上面代码大致实现将异步代码从函数嵌套转为同步书写风格,避免回调地狱问题。如果业务场景需要更深层的嵌套异步操作,只需要在 then函数的成功回调内继续 返回接下来的异步操作的 promise对象,支持链式调用。这种书写方式更容易维护。异常处理可以在then函数第二个参数内写,也可以统一在catch()里面做处理。

Promise.resolve(value)

接着说,如果链式调用里then函数成功回调内,我们需要逻辑判断是否返回异步操作的 promise对象,或同步返回具体结果。但同时又希望链式调用能够维持。这时我们可以采用Promise.resolve(value)语法,示例:

wxLogin().then((res) => {
  code = res.code

  // 微信登录成功后,判断之前是否缓存过用户信息
  // 如果缓存过直接去本地缓存信息
  // 如果未缓存,则需要异步请求用户信息
  let userInfo = getStorageUserInfo()
  if (userInfo) {
    return Promise.resolve(userInfo)
  } else {
    return wxGetUserInfo()
  }
}).then((res) => {
  // ... 后续业务处理
}).catch((err) => {
  // 异常处理
  console.log(`err = ${err}`)
})

Promise.reject(reason)

再有,假如我们在promise的链式调用中的遇到在 then函数成功回调内进行逻辑判断,需要根据接口返回的数据进行异常处理。假如接口数据返回有问题,不希望后面promise继续执行。则可以采用Promise.reject(reason)语法。比如通常接口返回数据会携带code字段,code非0表示接口异常需要异常处理,那么可以采用下面的写法:

getIndexinfo(app).then((res) => {
  // code 不为 0,返回错误异常
  if (res.code == 0) {
    // 接口数据成功处理
    this.setData({
      info: res.data
    })
  } else {
    return Promise.reject('接口返回异常')
  }
}).catch((err) => {
  console.log(`err = ${err}`)
})

Promise.all(iterable)

更复杂的场景,如果我们在链式调用的过程中有一个then函数内部需要同时做多个异步操作,后面异步操作需要在前面同时进行的异步操作结束返回结果后执行。那么可以使用Promise.all(iterable)语法,then函数的成功回调会拿到由所有promise返回数据组成的数组,顺序与promise.all传递数组顺序一致。业务场景示例,在小程序中我们需要首先获取商品的基本信息,通过商品的基本信息获取服务时间和商品服务项。

// 获取商品信息
// 根据商品信息获取,商品服务时间以及服务项
getProductinfo(app).then((res) => {
  // code 不为 0,返回错误异常
  if (res.code == 0) {
    // 接口数据成功处理
    this.setData({
      info: res.data
    })

    // 同时获取服务时间和服务项
    // getServiceTime 获取服务时间 promise 接口
    // getServiceType 获取商品服务项 promise 接口
    return Promise.all([getServiceTime, getServiceType])
  } else {
    return Promise.reject('接口返回异常')
  }
}).then((res) => {
  // res 是包含Promise.all里所有promise返回值的数组
  // ...业务处理
}).catch((err) => {
  console.log(`err = ${err}`)
})

Promise.race(iterable)

同时也存在Promise.race(iterable)语法,当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

以上简单将 Promise 语法和实际场景中业务逻辑结合在一起进行演示。采用promise 语法替代回调函数处理异步代码更直观。

ES7 async/await语法

虽然目前 promise 已经可以将嵌套函数进行展平,但是写代码和阅读依然有额外的负担。在ES7中有了更加标准的解决方案,新增 async/await 两个关键词。async 声明一个异步函数,await 操作符用来等待 promise 或任何值。

await 表达式会造成异步表达式停止执行并且等待 promise 的完成,当值被 resolved,异步函数会恢复执行以及返回 resolved 值。如果该值不是一个 promise,它将会被转换成一个 resolved 后的 promise。如果 promise 被 rejected,await 表达式会抛出异常值。

async/await示例

// 登录流程
(async () => {
  try {
    // 异步调用微信登录
    let code = await wxLogin()

    // 异步获取用户信息
    let userInfo = await wxGetUserInfo()

    // 异步请求业务登录接口
    let data = {
      code,
      ...userInfo
    }
    let djLoginInfo = await requestDjLogin(data)

    // 存储业务登录信息
    // 后续处理...
  } catch (err) {
    // 异常处理
    console.log(`err = ${err} `)
  }

})()

wxLogin、wxGetUserInfo、requestDjLogin 函数同样为返回 promise 对象的异步操作。可以看出,async/awit 语法相对于 promise().then()更加简洁清晰。上面三个异步请求代码书写方式变成顺序书写,不存在回调函数嵌套问题。如果遇到同时执行多个异步操作的场景需要使用前面提到的 Promise.all([]) 语法。

let info = await Promise.all([getServiceTime, getServiceType])

await 异常处理

await 异步操作 promise 函数中,reject异常可以通过外部 try catch 语法获取。另外如果需要在逻辑判断的地方手动抛出异常,可以采用 await Promise.reject('接口异常') 语法。外部使用 try catch语句内进行处理。

总结

在前端可能不会遇到太深的嵌套回调问题,在小程序场景下api大部分为异步调用,异步代码嵌套使用场景也更丰富。es6、es7语法对这个问题提出新的解决方式,promise、async/await语法。通过新语法可以将异步嵌套代码变得顺序执行,书写方便更容易维护和理解。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-07-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 交互设计前端开发与后端程序设计 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Promise对象
  • Promise.resolve(value)
  • Promise.reject(reason)
  • Promise.all(iterable)
  • Promise.race(iterable)
  • ES7 async/await语法
  • async/await示例
  • await 异常处理
  • 总结
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档