微信小程序中使用 async/await的方法实例分析


Posted in Javascript onMay 06, 2020

本文实例讲述了微信小程序中使用 async await的方法。分享给大家供大家参考,具体如下:

微信小程序中有大量接口是异步调用,比如 wx.login()wx.request()wx.getUserInfo() 等,都是使用一个对象作为参数,并定义了 success()fail()complete() 作为异步调用不同情况下的回调。

但是,以回调的方式来写程序,真的很伤,如果有一个过程需要依次干这些事情:

  • wx.getStorage() 获取缓存数据,检查登录状态
  • wx.getSetting() 获取配置信息,
  • wx.login() 使用配置信息进行登录
  • wx.getUserInfo() 登录后获取用户信息
  • wx.request() 向业务服务器发起数据请求
  • ……

那么,代码大概会长这样

wx.getStorage({
  fail: () => {
    wx.getSetting({
      success: settings => {
        wx.login({
          success: ({ code }) => {
            wx.getUesrInfo({
              code,
              success: (userInfo) => {
                wx.request({
                  success: () => {
                    // do something
                  }
                });
              }
            });
          }
        });
      }
    });
  }
});

显然,async/await 可以同样逻辑的代码看起来舒服得多。不过默认情况下,“微信开发者工具”并不支持 async/await。如何启用?

1. 用上 async/await

如果有心,在微信小程序官方文档中搜索 async 可以找到“工具⇒开发辅助⇒代码编译”页面中提到了对 async/await 的支持情况,是在“增加编译”小节的一个表格中,摘录一段:

1.02.1904282 以及之后版本的开发工具中,增加了增强编译的选项来增强 ES6 转 ES5 的能力,启用后会使用新的编译逻辑以及提供额外的选项供开发者使用。

特性 原有逻辑 增强编译
Async/Await 不支持 支持
  • 支持 async/await 语法,按需注入 regeneratorRuntime,目录位置与辅助函数一致

总之呢,就是,只要把“微信开发者工具”更新到 v1.02.1904282 以上,就不需要干 npm install regenerator 这之类的事情,只需要修改一个配置项就能使用 async/await 特性了。这个配置就在“工具栏⇒详情⇒本地设置”页面中。

微信小程序中使用 async/await的方法实例分析

为了快速验证 async/await 可用,在 app.jsonLaunch() 事件函数中加一段代码:

(async () => {
  const p = await new Promise(resolve => {
    setTimeout(() => resolve("hello async/await"), 1000);
  });
  console.log(p);
})();

在短暂的自动编译运行之后,在调试器界面的 Console 页签中可以看到输出:

hello async/await

如果不行,请先检查“微信开发者工具”的版本——至少,去下载一个最新版本总不会有问题的。

2. 改造 wx.abcd 异步方法

虽然 async/await 得到了支持,但是还得把 wx.abcd() 封装成 Promise 风格才行。

Node.js 在 util 模块中提供了 promisify 来把 Node.js 风格的回调转换成 Promise 风格,但显然它不适用于 wx 风格。还是自己动手吧,也不用考虑太多,比如 wx 风格的异步调用在形式上都是一致的,它们的特征如下 :

  • 使用一个对象传递所有参数,包括三个主要的回调
  • success: (res) => any 在异步方法成功时回调
  • fail: (err) => any 在异步方法失败时回调
  • complete: () => any 在异步方法完成(不管成功还是失败)时回调

所以,如果 wx.abcd() 改成了 Promise 风格,通过 async/await 来编写,大概应该是这个样子

try {
  const res = wx.abcd();
  // do anything in success callback
} catch (err) {
  // do anything in fail callback
} finally {
  // do anything in complete callback
}

当然,catchfinally 这两个部分并不是必须,也就是说,不一定非得用 try 语句块。但是,如果不用 catch,会有一个神坑存在,这个问题后面再说。现在首先要做的是改造

2.1. 定义 promisify()

promisify() 就是一个封装函数,传入原来的 wx.abcd 作为参加,返回一个 Promise 风格的新函数。代码和解释如下:

function promisify(fn) {
  // promisify() 返回的是一个函数,
  // 这个函数跟传入的 fn(即 wx.abcd) 签名相同(或兼容)
  return async function(args) {
  //          ^^^^ 接受一个单一参数对象
    return new Promise((resolve, reject) => {
  //       ^^^^^^^^^^^ 返回一个 Promise 对象
      fn({
  //   ^^ ^ 调用原函数并使用改造过的新的参数对象
        ...(args || {}),
  //     ^^^^^^^^    这个新参数对象得有原本传入的参数,
  //           ^^ 当然得兼容没有传入参数的情况
        success: res => resolve(res),
  //     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 注入 success 回调,resovle 它
        fail: err => reject(err)
  //     ^^^^^^^^^^^^^^^^^^^^^^^^ 注入 fail 回调,reject 它
      });
    });
  };
}

举例使用它:

const asyncLogin = promisify(wx.login); // 注意别写成 wx.login(),为什么,我不说
try {
  const res = asyncLogin();
  const code = res.code;
  // do something with code
} catch (err) {
  // login error
} finally {
  // promisify 里没有专门注入 complete 回调,
  // 因为 complete 的内容可以写在这里
}

2.2. 定义 wx.async()

不过老实说,把要用的异步方法通过 promisify 一个个处理,写起来还是挺烦的,不如写个工具函数把要用的方法一次性转换出来。不过一查,wx 下定义了不知道多少异步方法,还是退而求其次,用到啥转啥,不过可以批量转,转出来的结果还是封装在一个对象中。整个过程就是迭代处理,最后把每个处理结果聚焦在一起:

function toAsync(names) {  // 这里 names 期望是一个数组
  return (names || [])
    .map(name => (
      {
        name,
        member: wx[name]
      }
    ))
    .filter(t => typeof t.member === "function")
    .reduce((r, t) => {
      r[t.name] = promisify(wx[t.name]);
      return r;
    }, {});
}

这个 toAsync 的用法大致是这样的

const awx = toAsync(["login", "request"]);
await awx.login();
await awx.request({...});

有些人可能更习惯单个参数传入的方式,像这样

const awx = toAsync("login", "request");

那么在 toAsync 的定义中,参数改为 ...names 就好,即

function toAsync(...names) { ... }

还没完,因为我不想在每一个 JS 文件中去 import { toAsync } from ...。所以把它在 App.onLaunch() 中把它注入到 wx 对象中去,就像这样

App({
  onLaunch: function() {
    // ...
    wx.async = toAsync;
    // ...
  }
});

3. await 带来的神坑

工具准备好了,代码也大刀阔斧地进行了改造,看起来舒服多了,一运行却报错!为什么???

先来看一段原来的代码,是这样的

wx.getStorage({
  key: "blabla",
  success: res => {
    // do with res
  }
});

改造之后是这样

const res = await awx.getStorage({ key: "blabla" }); // <== runtime error
// do with res

awx.getStorage 抛了个异常,原因是叫 "blabal" 的这个数据不存在。

为什么原来没有错,现在却报错?

因为原来没有定义 fail 回调,所以错误被忽略了。但是 promisify()fail 回调封装成了 reject(),所以 awx.getStorage() 返回的 Promise 对象上,需要通过 catch() 来处理。我们没有直接使用 Promise 对象,而是用的 await 语法,所以 reject() 会以抛出异常的形式体现出来。

用人话说,代码得这样改:

try {
  const res = await awx.getStorage({ key: "blabla" }); // <== runtime error
  // do with res
} catch (err) {
  // 我知道有错,就是当它不存在!
}

伤心了不是?如果没有伤心,你想想,每一个调用都要用 try ... catch ... 代码块,还能不伤心吗?

3.1. 忽略不需要处理的错误

处理错误真的是个好习惯,但真的不是所有错误情况都需要处理。其实要忽略错误也很简单,直接在每个 Promise 形式的异步调后面加句话就行,比如

const res = await awx
  .getStorage({ key: "blabla" })
  .catch(() => {});
// ^^^^^^^^^^^^^^^^ 捕捉错误,但什么也不干

稍微解释一下,在这里 awx.getStorage() 返回一个 Promise 对象,对该对象调用 .catch() 会封装 reject 的情况,同时它会返回一个新的 Promise 对象,这个对象才是 await 等待的 Promise。

不过感觉 .catch(() => {}) 写起来怪怪的,那就封装成一个方法吧,这得改 Promise 类的原形

Promise.prototype.ignoreError = function() {
  return this.catch(() => { });
};

这段代码放在定义 toAsync() 之前就好。

用起来也像那么回事

const res = await awx
  .getStorage({ key: "blabla" })
  .ignoreError();

对于单个 await 异步调用,如果不想写 try ... catch ... 块,还可以自己定义一个 ifError(fn) 来处理错误的情况。但是如果需要批量处理错误,还是 try ... catch ... 用起顺手:

4. 回到开始

try {
  const storeValue = await awx.getStorage({});
  const settings = await awx.getSetting();
  const { code } = await awx.login();
  const userInfo = await awx.getUserInfo({ code });
} catch (err) {
  // 处理错误吧
}

看,不需要对每个异步调用定义 fail 回调,一个 try ... catch ... 处理所有可能产生的错误,这可不也是 async/await 的优势!

希望本文所述对大家微信小程序设计有所帮助。

Javascript 相关文章推荐
javascript 年月日联动实现核心代码
Dec 21 Javascript
jquery实现侧边弹出的垂直导航
Dec 09 Javascript
JavaScript Ajax实现异步通信
Dec 14 Javascript
详解Vue.js动态绑定class
Dec 20 Javascript
vue.js 1.x与2.0中js实时监听input值的变化
Mar 15 Javascript
使用jQuery.Pin垂直滚动时固定导航
May 24 jQuery
vue loadmore组件上拉加载更多功能示例代码
Jul 19 Javascript
浅谈React + Webpack 构建打包优化
Jan 23 Javascript
JavaScript使用math.js进行精确计算操作示例
Jun 19 Javascript
jquery操作checkbox的常用方法总结【附测试源码下载】
Jun 10 jQuery
微信小程序图片自适应实现解析
Jan 21 Javascript
vue radio单选框,获取当前项(每一项)的value值操作
Sep 10 Javascript
JavaScript常用工具函数大全
May 06 #Javascript
详解react组件通讯方式(多种)
May 06 #Javascript
Node.js API详解之 os模块用法实例分析
May 06 #Javascript
js实现无缝轮播图特效
May 09 #Javascript
js实现上传按钮并显示缩略图小轮子
May 04 #Javascript
js代码实现轮播图
May 04 #Javascript
原生js实现轮播图特效
May 04 #Javascript
You might like
提问的智慧
2006/10/09 PHP
Windows中安装Apache2和PHP4权威指南
2006/11/18 PHP
PHP优于Node.js的五大理由分享
2012/09/15 PHP
PHP中数字检测is_numeric与ctype_digit的区别介绍
2012/10/04 PHP
利用php递归实现无限分类 格式化数组的详解
2013/06/08 PHP
PHP四种排序算法实现及效率分析【冒泡排序,插入排序,选择排序和快速排序】
2018/04/27 PHP
Yii2压缩PHP中模板代码的输出问题
2018/08/28 PHP
javaScript 读取和设置文档元素的样式属性
2009/04/14 Javascript
快速解决FusionCharts联动的中文乱码问题
2013/12/04 Javascript
JavaScript实现梯形乘法表的方法
2015/04/25 Javascript
Bootstrap Table使用方法详解
2016/08/01 Javascript
vue 指定组件缓存实例详解
2018/04/01 Javascript
vue从一个页面跳转到另一个页面并携带参数的解决方法
2019/08/12 Javascript
JS如何在数组指定位置插入元素
2020/03/10 Javascript
[19:24]DOTA2客户端使用指南 一分钟快速设置轻松超神
2013/09/24 DOTA
[02:36]DOTA2上海特锦赛 回忆电竞生涯的重要瞬间
2016/03/25 DOTA
深入浅析python3中的unicode和bytes问题
2019/07/03 Python
win10下python3.8的PIL库安装过程
2020/06/08 Python
白色公司:The White Company
2017/10/11 全球购物
澳大利亚家具和家居用品在线:BROSA
2017/11/02 全球购物
Omio俄罗斯:一次搜索公共汽车、火车和飞机的机票
2018/11/17 全球购物
波兰多品牌运动商店:StreetStyle24.pl
2020/09/22 全球购物
系统管理员的职责包括那些?管理的对象是什么?
2016/09/20 面试题
研究生自荐信
2013/10/09 职场文书
简短证婚人证婚词
2014/01/09 职场文书
药品营销策划方案
2014/06/15 职场文书
心理学专业求职信
2014/06/16 职场文书
后勤管理员岗位职责
2014/08/27 职场文书
党员教师个人对照检查材料(群众路线)
2014/09/26 职场文书
2015年乡镇人大工作总结
2015/04/22 职场文书
2015年乡镇妇联工作总结
2015/05/19 职场文书
贫困证明书范文
2015/06/16 职场文书
小学体育队列队形教学反思
2016/02/16 职场文书
比较几种Redis集群方案
2021/06/21 Redis
十大最强岩石系宝可梦,怪颚龙实力最强,第七破坏力很强
2022/03/18 日漫
vue选项卡切换的实现案例
2022/04/11 Vue.js