鄒捷
(國防大學政治學院,西安 710068)
分析微信小程序異步API 的特點,針對其在某些應用場景下不適用的情況,給出一種高效、通用的解決方案,實現在調用異步方式獲取的關鍵變量之前,檢查、等待、確保變量有意義的同步操作。
微信小程序;異步;同步化
微信小程序自2017年1月推出以來,保持了高速發展。據統計,2018年底小程序數量已經超過120 萬,用戶數量突破7 億,用戶對小程序的使用習慣已經形成。小程序成功的一個很大的原因,就是相對于App而言,其開發難度大大降低。開發者只需具備一定的Web 及前端開發的經驗,就可以通過小程序開發框架,簡單、高效地開發出媲美原生App 的應用。小程序框架系統分為視圖層和邏輯層兩部分,視圖層主要通過視圖層描述語言WXML 和WXSS 完成開發,其功能與HTML 和CSS 類似;邏輯層通過JavaScript 進行開發。在開發實踐中,由于小程序API 的設計和限制,邏輯層的處理和常規腳本有一定的區別。本文針對微信小程序API 異步處理方式帶來的一些問題進行分析,并結合實踐提出一套可行的解決方案。
微信小程序提供了大量的API,如用戶界面、網絡訪問、數據緩存、媒體訪問、開放接口等,大多數API 都采用了異步方式處理返回值。如發起網絡請求的wx.request[1],其處理方式如下:
wx.request({

wx.request 提交網絡訪問請求,訪問Web 頁面https://test.com/test.php,該頁面既可以返回標準的HTML 代碼,也可以返回JSON 字符串形式的數據,對應的處理在success 指定的回調函數中進行。微信小程序中,對于回調的處理都采用了異步方式,也就意味著request 訪問請求提交后,程序并不會停下來等待返回值,而是繼續執行request 后續的代碼。當頁面的返回值到達時,再執行success 指定的函數。這種異步方式處理的好處是程序執行效率高,由于網絡速度各有不同,返回的時間可能很快,也可能很慢,采用異步方式處理返回值,不會受網速影響出現卡頓現象。
但在一些特定的應用場景下,異步方式也存在不足。
第一,代碼結構變得復雜。由于返回值處理代碼必須寫在success 處理函數中,如果有多個相互依賴的網絡訪問,結構就必須多次嵌套,可讀性將大大降低。如獲取用戶信息getUserInfo 必須要在獲取配置信息getSetting 成功之后執行,其結構就變為:

第二,對于一些需要從網絡獲取的關鍵變量,在小程序的多個頁面或多個位置,都可能被調用。但因為從網絡獲取方法wx.request 是異步方式的,因此不能保證在需要調用該關鍵變量時,它已經被獲取到了。例如用戶唯一標識openid 需要通過網絡獲取,盡管小程序一運行就優先執行了獲取操作getOpenid,但由于是異步操作,可能openid 還沒有從網絡傳輸過來,程序已經執行到某個需要用到openid 的代碼了,這種情況將導致程序異常。

問題二比問題一更加突出,因為用到openid 的地方很多,即使想把處理代碼都搬到getOpenid 的success回調函數中也是不可能的。解決方法只能是對小程序中通過異步方式獲取的數據,增加同步點。在網絡返回結果到達之前,不往后執行,等待直到網絡返回結果到達或者超時。
查詢網絡上的類似解決方案,一種方法是使用Promise 對象實現異步轉同步[2],但在小程序的JS 環境中無法通過。另一種方法是在調用關鍵變量之前,判斷變量是否存在。如果變量尚未賦值,則等待一個時間片后再判斷。但由于JavaScript 是單線程處理,這種方法最終將導致CPU 時間被占滿,程序卡死。
經過反復實踐,筆者找到一種可行的關鍵變量同步方法,并將其通用化。各種需要等待同步的關鍵變量,都可以使用這種方法。其核心是waitVar 函數,實現對變量的等待。傳遞給它的有三個參數:key 是變量的名稱,可以保證多個調用可以同時執行;varb 是關鍵變量本身;fun 是調用關鍵變量的函數,由waitVar 在必要的時候調用該函數。當varb 已經有值的時候,wait-Var 返回1;當varb 尚未賦值的時候,返回-1,并且會在500 毫秒后再次運行fun 函數;waitVar 保持一個內部計數,當嘗試次數過多時認定為超時,返回-2,不再運行fun 函數。waitVar 核心代碼如下:
//等待指定變量,返回:1:成功-1:等待-2:超時

在需要同步變量的函數的開始位置調用waitVar,如果返回-1 或-2,則直接退出,由waitVar 控制在500毫秒后重新進入該函數;如果返回1,則正常往下執行。如上述的getCataTag 函數中,必須要用到openid,可以在函數開始處調用waitVar。
getCataTag:function(overwrite){
if (util.waitVar("openid", this.openid, this.getCataTag) <0)return //強制等待openid
……//后續代碼
當waitVar 返回1,意味著openid 已經有值了,繼續向下執行;如果返回值為-1,則退出,由waitVar 在500 毫秒后重新調用getCataTag 函數;如果返回-2,顯示超時提示并退出后,不會再進入getCataTag 函數。這樣的處理,既保證了程序運行順暢,也考慮到極端網絡超時的情況,確保程序不會崩潰。

圖1 網絡異常時提示
除了openid,其他任何類似情況都可以使用這種機制。如graph.js 是圖表顯示模塊,當從其他頁面跳轉到圖表頁面時,會先通過網絡獲取圖表所需的數據并存儲在allinfo 數組中,然后在graph 的onShow 函數中顯示圖表。因此,必須要保證onShow 執行時allinfo 中的數據已經獲取到,使用waitVar 就能夠確保這一同步操作。
onShow: function () { //顯示圖表,前提是數組allinfo 必須已經有值
//如果數組allinfo 尚無數據,則退出并在500 毫秒后重新執行onShow
//如果數組allinfo 已經有數據,則繼續執行后續圖表顯示代碼
if (util.waitVar("graphallinfo", allinfo, this.onShow) <0)return
……//后續代碼
以下是實際運行效果,依次同步等待了兩個關鍵變量:openid 和info_catatag,且info_catatag 的依賴于openid。圖2 可以看出,程序在兩個同步點同時等待,直到openid 和info_catatag 依次取得數據,程序再繼續向下運行。

圖2 依次同步openid和info_catatag
本文針對微信小程序異步API 在某些應用場景下不適用的情況,給出了一種高效、通用的同步方法。該方法經過多個小程序的應用檢驗,在各種網絡條件和各種機型的手機上,都能夠順暢地運作,對于小程序開發有一定的參考價值。