微信小程序中有大量的接口是异步调用的,比如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:()=>{//做一点事}});}});}});}});}});显然,async/await可以让同样逻辑的代码看起来舒服很多。但是,默认情况下,“微信开发者工具”不支持async/await。如何启用它?1、如果你对async/await感兴趣,可以在微信小程序官方文档中搜索async,会发现在“工具?开发辅助?代码编译”页面中提到了对async/await的支持,即在“添加编译”部分的一个表格中,摘录了一段:在1.02.1904282及以后版本的开发工具中,增加了一个增强编译的选项,用于增强ES6转ES5的能力。启用后,将使用新的编译逻辑,并提供额外的选项供开发者使用。.Features原有逻辑增强编译Async/Await不支持支持async/await语法,按需注入regeneratorRuntime,目录位置与辅助功能一致总之只要更新“微信开发者工具”到v1。不需要像npminstallregenerator这样的操作,只需要修改一个配置项就可以使用async/await特性。这个配置在“工具栏?详细信息?本地设置”页面。为了快速验证async/await可用,在app.js的onLaunch()事件函数中加入一段代码:(async()=>{constp=awaitnewPromise(resolve=>{setTimeout(()=>resolve("helloasync/await"),1000);});console.log(p);})();经过短暂的自动编译运行,在调试器界面的Console选项卡中可以看到输出:helloasync/await如果没有,请先检查“微信开发者工具”的版本——至少,下载最新版本不会有问题。2.改造wx.abcd异步方法虽然支持async/await,但是wx.abcd()必须封装成Promise样式。Node.js在util模块中提供了promisify将Node.js风格的回调转换为Promise风格,但显然它不适用于wx风格。自己做,不用想太多。比如wx风格的异步调用在形式上是一致的,它们的特点如下:使用一个对象来传递所有的参数,包括三个主要的回调success:(res)=>anyasync方法成功时回调失败:(err)=>anyCallbackwhentheasyncmethodfailscomplete:()=>anyCallbackwhentheasyncmethodcomplete(不管成功或失败)所以,如果把wx.abcd()改成Promise风格,通过async写/await,大概是这样的也就是说,没有必要使用try语句块。但是如果不使用catch,就会有一个坑,后面会讲到。现在要做的第一件事就是改造。2.1.定义promisify()promisify()是一个包装函数,将原来的wx.abcd作为参与者传入,返回一个Promise风格的新函数。代码及解释如下:functionpromisify(fn){//promisify()返回一个函数,//这个函数与传入的fn(即wx.abcd)具有相同(或兼容)的签名returnasyncfunction(args){//^^^^接受一个单参数对象returnnewPromise((resolve,reject)=>{//^^^^^^^^^^^^^返回一个Promise对象fn({//^^^调用原函数并使用修改后的新参数对象...(args||{}),//^^^^^^^^^这个新参数对象必须有原来的参数,//^^必须兼容没有参数的情况success:res=>resolve(res),//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^注入成功回调,resovle失败:err=>reject(err)//^^^^^^^^^^^^^^^^^^^^^^^^^^注入失败回调,拒绝});});};}使用举个例子:constasyncLogin=promisify(wx.login);//注意不要写wx.login(),为什么,我不说try{constres=asyncLogin();constcode=res.code;//dosomethingwithcode}catch(err){//loginerror}finally{//pro中没有专门注入的完整回调misify,//因为这里可以写complete的内容}2.2.定义wx.async()不过说实话,通过promisify一个一个处理要用到的异步方法,写起来挺烦的,不如写个实用函数一次性转换要用到的方法.不过查了一下,wx定义了很多异步方法,用什么转就退而求其次,不过可以批量转,转的结果还是封装在一个对象中。整个过程是迭代处理,最后将每个处理结果集中在一起:functiontoAsync(names){//这里,names应该是一个数组return(names||[]).map(name=>({name,成员:wx[name]})).filter(t=>typeoft.member==="function").reduce((r,t)=>{r[t.name]=promisify(wx[t.name]);returnr;},{});}这个toAsync的用法大致像constawx=toAsync(["login","re??quest"]);awaitawx.login();awaitawx.request({...});人们可能更习惯于传入单个参数的方式,像这样constawx=toAsync("login","re??quest");那么在toAsync的定义中,参数要改成...names,也就是functiontoAsync(...names){...}还没完,因为我不想import{toAsync}from。..在每个JS文件中。所以把它注入到App.onLaunch()中的wx对象中,像这样App({onLaunch:function(){//...wx.async=toAsync;//...}});3。await带来的魔坑工具已经准备就绪,代码也进行了大刀阔斧的修改。看起来舒服多了,但是运行起来就报错!为什么???我们先看一下原代码,它是这样的:wx.getStorage({key:"blabla",success:res=>{//dowithres}});改造后,看起来像这样constres=awaitawx.getStorage({key:"blabla"});//<==runtimeerror//dowithresawx.getStorage抛出异常,因为名为"blabal"的数据不存在。为什么之前没有错误,现在却报错?因为最初没有定义失败回调,所以错误被忽略了。但是promisify()将fail回调封装到了reject()中,所以awx.getStorage()返回的Promise对象需要catch()处理。我们没有直接使用Promise对象,而是使用了await语法,所以reject()会以抛出异常的形式体现出来。用人的话来说,代码得改成这样:错了,就在它不存在的时候!}可悲是不是?如果你不难过,想想看,每次调用都要用一个try...catch...代码块,你能不难过吗?3.1.忽略不需要处理的错误错误确实是一种很好的做法,但并不是所有的错误情况都真正需要处理。其实忽略错误很简单,只要在每次Promise风格的异步调用后加一句,比如constres=awaitawx.getStorage({key:"blabla"}).catch(()=>{});//^^^^^^^^^^^^^^^^^^^^^^^Catch错误,但是什么都不做解释一下,这里awx.getStorage()返回一个Promise对象,调用对象上的.catch()就会封装拒绝情况,同时会返回一个新的Promise对象,也就是await等待的Promise。但是我觉得.catch(()=>{})写起来怪怪的,所以我们把它封装成一个方法,这个方法要改变Promise类的原型。Promise.prototype.ignoreError=function(){returnthis.catch(()=>{});};只需将这段代码放在定义toAsync()之前。它的工作原理就像constres=awaitawx.getStorage({key:"blabla"}).ignoreError();对于单个await异步调用,如果你不想写一个try...catch...块,你也可以自己定义一个ifError(fn)来处理错误情况。但是如果需要批量处理错误,try...catch...很好用:4.回到开头try{conststoreValue=awaitawx.getStorage({});constsettings=awaitawx.getSetting();const{code}=awaitawx.login();constuserInfo=awaitawx.getUserInfo({code});}catch(err){//handletheerror}看,不用为每个异步调用都定义一个fail回调,一个试着抓..。处理所有可能的错误,这不也是async/await的优势吗!
