天天看點

在微信小程式中使用 async/await

微信小程式中有大量接口是異步調用,比如 <code>wx.login()</code>、<code>wx.request()</code>、<code>wx.getuserinfo()</code> 等,都是使用一個對象作為參數,并定義了 <code>success()</code>、<code>fail()</code> 和 <code>complete()</code> 作為異步調用不同情況下的回調。

但是,以回調的方式來寫程式,真的很傷,如果有一個過程需要依次幹這些事情:

<code>wx.getstorage()</code> 擷取緩存資料,檢查登入狀态

<code>wx.getsetting()</code> 擷取配置資訊,

<code>wx.login()</code> 使用配置資訊進行登入

<code>wx.getuserinfo()</code> 登入後擷取使用者資訊

<code>wx.request()</code> 向業務伺服器發起資料請求

……

那麼,代碼大概會長這樣

顯然,async/await 可以同樣邏輯的代碼看起來舒服得多。不過預設情況下,“微信開發者工具”并不支援 async/await。如何啟用?

如果有心,在微信小程式官方文檔中搜尋 <code>async</code> 可以找到“工具⇒開發輔助⇒代碼編譯”頁面中提到了對 async/await 的支援情況,是在“增加編譯”小節的一個表格中,摘錄一段:

在 1.02.1904282 以及之後版本的開發工具中,增加了增強編譯的選項來增強 es6 轉 es5 的能力,啟用後會使用新的編譯邏輯以及提供額外的選項供開發者使用。 特性 原有邏輯 增強編譯 async/await 不支援 支援 支援 async/await 文法,按需注入 <code>regeneratorruntime</code>,目錄位置與輔助函數一緻

總之呢,就是,隻要把“微信開發者工具”更新到 <code>v1.02.1904282</code> 以上,就不需要幹 <code>npm install regenerator</code> 這之類的事情,隻需要修改一個配置項就能使用 <code>async/await</code> 特性了。這個配置就在“工具欄⇒詳情⇒本地設定”頁面中。

在微信小程式中使用 async/await

為了快速驗證 async/await 可用,在 <code>app.js</code> 的 <code>onlaunch()</code> 事件函數中加一段代碼:

在短暫的自動編譯運作之後,在調試器界面的 console 頁簽中可以看到輸出:

如果不行,請先檢查“微信開發者工具”的版本——至少,去下載下傳一個最新版本總不會有問題的。

雖然 async/await 得到了支援,但是還得把 <code>wx.abcd()</code> 封裝成 promise 風格才行。

node.js 在 util 子產品中提供了 <code>promisify</code> 來把 node.js 風格的回調轉換成 promise 風格,但顯然它不适用于 wx 風格。還是自己動手吧,也不用考慮太多,比如 wx 風格的異步調用在形式上都是一緻的,它們的特征如下 :

使用一個對象傳遞所有參數,包括三個主要的回調

<code>success: (res) =&amp;gt; any</code> 在異步方法成功時回調

<code>fail: (err) =&amp;gt; any</code> 在異步方法失敗時回調

<code>complete: () =&amp;gt; any</code> 在異步方法完成(不管成功還是失敗)時回調

是以,如果 <code>wx.abcd()</code> 改成了 promise 風格,通過 async/await 來編寫,大概應該是這個樣子

當然,<code>catch</code> 和 <code>finally</code> 這兩個部分并不是必須,也就是說,不一定非得用 <code>try</code> 語句塊。但是,如果不用 <code>catch</code>,會有一個神坑存在,這個問題後面再說。現在首先要做的是改造。

<code>promisify()</code> 就是一個封裝函數,傳入原來的 <code>wx.abcd</code> 作為參加,傳回一個 promise 風格的新函數。代碼和解釋如下:

由于 51cto 部落格在顯示代碼時,部分字型不是等寬字型,純代碼張貼出來的标記不能對準,是以先貼圖。但為了友善試驗,接着貼代碼。
在微信小程式中使用 async/await

舉例使用它:

不過老實說,把要用的異步方法通過 <code>promisify</code> 一個個處理,寫起來還是挺煩的,不如寫個工具函數把要用的方法一次性轉換出來。不過一查,<code>wx</code> 下定義了不知道多少異步方法,還是退而求其次,用到啥轉啥,不過可以批量轉,轉出來的結果還是封裝在一個對象中。整個過程就是疊代處理,最後把每個處理結果聚焦在一起:

這個 <code>toasync</code> 的用法大緻是這樣的

有些人可能更習慣單個參數傳入的方式,像這樣

那麼在 <code>toasync</code> 的定義中,參數改為 <code>...names</code> 就好,即

還沒完,因為我不想在每一個 js 檔案中去 <code>import { toasync } from ...</code>。是以把它在 <code>app.onlaunch()</code> 中把它注入到 <code>wx</code> 對象中去,就像這樣

工具準備好了,代碼也大刀闊斧地進行了改造,看起來舒服多了,一運作卻報錯!為什麼???

先來看一段原來的代碼,是這樣的

改造之後是這樣

<code>awx.getstorage</code> 抛了個異常,原因是叫 <code>"blabal"</code> 的這個資料不存在。

為什麼原來沒有錯,現在卻報錯?

因為原來沒有定義 <code>fail</code> 回調,是以錯誤被忽略了。但是 <code>promisify()</code> 把 <code>fail</code> 回調封裝成了 <code>reject()</code>,是以 <code>awx.getstorage()</code> 傳回的 promise 對象上,需要通過 <code>catch()</code> 來處理。我們沒有直接使用 promise 對象,而是用的 <code>await</code> 文法,是以 <code>reject()</code> 會以抛出異常的形式展現出來。

用人話說,代碼得這樣改:

傷心了不是?如果沒有傷心,你想想,每一個調用都要用 <code>try ... catch ...</code> 代碼塊,還能不傷心嗎?

處理錯誤真的是個好習慣,但真的不是所有錯誤情況都需要處理。其實要忽略錯誤也很簡單,直接在每個 promise 形式的異步調後面加句話就行,比如

稍微解釋一下,在這裡 <code>awx.getstorage()</code> 傳回一個 promise 對象,對該對象調用 <code>.catch()</code> 會封裝 reject 的情況,同時它會傳回一個新的 promise 對象,這個對象才是 <code>await</code> 等待的 promise。

不過感覺 <code>.catch(() =&amp;gt; {})</code> 寫起來怪怪的,那就封裝成一個方法吧,這得改 <code>promise</code> 類的原形

這段代碼放在定義 <code>toasync()</code> 之前就好。

用起來也像那麼回事

對于單個 <code>await</code> 異步調用,如果不想寫 <code>try ... catch ...</code> 塊,還可以自己定義一個 <code>iferror(fn)</code> 來處理錯誤的情況。但是如果需要批量處理錯誤,還是 <code>try ... catch ...</code> 用起順手:

看,不需要對每個異步調用定義 <code>fail</code> 回調,一個 <code>try ... catch ...</code> 處理所有可能産生的錯誤,這可不也是 async/await 的優勢!

繼續閱讀