小程序異步問題之多個(gè)網(wǎng)絡(luò)請(qǐng)求依次執(zhí)行并依次收集請(qǐng)求結(jié)果
業(yè)務(wù)邏輯
最近開發(fā)一個(gè)便簽小程序的時(shí)候,有這樣一個(gè)需求:用戶可以在寫便簽的時(shí)候添加一個(gè)或多個(gè)圖片。
對(duì)于這個(gè)需求,我們用戶按下保存鍵時(shí),內(nèi)部具體的實(shí)現(xiàn)上是這樣的邏輯:
- 首先檢測(cè)用戶是否傳入了圖片,如果存儲(chǔ)本地圖片地址的數(shù)組長(zhǎng)度>=1,則將圖片數(shù)組放入上傳圖片的函數(shù)。
- 由于小程序網(wǎng)絡(luò)請(qǐng)求大小限制,我們只能采取循環(huán)上傳單文件,然后收集每次請(qǐng)求的結(jié)果--圖片在服務(wù)器的地址,最后將結(jié)果放在一個(gè)數(shù)組中供后續(xù)的操作使用。
- 當(dāng)圖片上傳函數(shù)全部執(zhí)行完畢后,將數(shù)組中的圖片數(shù)組取出來,賦值到日記對(duì)象中,再將整個(gè)日記對(duì)象提交到服務(wù)器。
- 服務(wù)器返回保存成功或失敗。
思路其實(shí)非常清晰簡(jiǎn)單,但是在代碼實(shí)現(xiàn)上卻翻了大跟頭。
異步帶來的問題
小程序的網(wǎng)絡(luò)請(qǐng)求是異步的:我們無法通過return來將網(wǎng)絡(luò)請(qǐng)求結(jié)果返回出來使用。
wx.request({ //...省略其他屬性 success: function (res) { }, fail: function (res) { } })
例如在微信中發(fā)送網(wǎng)絡(luò)請(qǐng)求,我們只能使用微信提供的方法wx.xxx,其中請(qǐng)求的結(jié)果保存在res中,而res無法直接return得到。
解決:res雖然無法直接獲取,但是我們能通過將需要使用到這個(gè)請(qǐng)求結(jié)果的業(yè)務(wù)邏輯代碼放入這個(gè)網(wǎng)絡(luò)請(qǐng)求的回調(diào)函數(shù)中直接讀取網(wǎng)絡(luò)請(qǐng)求結(jié)果,也就是一切都需要通過回調(diào)來解決。
wx.request({ //...省略其他屬性 success: function (res) { console.log(res); //接業(yè)務(wù)邏輯代碼 }, fail: function (res) { console.log(res); } })
例如這個(gè)微信的網(wǎng)絡(luò)請(qǐng)求,我們可以通過success和fail的回調(diào)函數(shù)來讀取res的值從而完成依賴res結(jié)果的業(yè)務(wù)邏輯。
回調(diào)地獄
雖然解決了結(jié)果獲取的問題,但是又產(chǎn)生了另一個(gè)問題,當(dāng)多個(gè)請(qǐng)求中有明確的先后順序時(shí),回調(diào)會(huì)嵌套的很厲害,造成回調(diào)地獄,代碼可讀性和可維護(hù)性都會(huì)很差。
例如對(duì)于一個(gè)日記頁(yè)面,需要先請(qǐng)求到頁(yè)面的數(shù)據(jù)(里面包含了圖片數(shù)據(jù)和其他數(shù)據(jù)的地址),再根據(jù)頁(yè)面數(shù)據(jù)去請(qǐng)求圖片數(shù)據(jù)后再請(qǐng)求音頻數(shù)據(jù)。例如以下代碼:
//請(qǐng)求頁(yè)面整體數(shù)據(jù) wx.request({ //...省略其他屬性 success: function (res) {//成功 //請(qǐng)求圖片數(shù)據(jù) wx.request({ success: function (res) {//成功 //請(qǐng)求音頻數(shù)據(jù) wx.request({ success: function (res) {//成功 }, fail: function (res) {//失敗 console.log("請(qǐng)求失敗:"+res); } }) }, fail: function (res) {//失敗 console.log("請(qǐng)求失敗:"+res); } }) }, fail: function (res) {//失敗 console.log("請(qǐng)求失敗:"+res); } })
如何優(yōu)化?幸運(yùn)的是,在es6里面我們可以用promise去優(yōu)化我們的回調(diào),用then代替回調(diào),首先將網(wǎng)絡(luò)請(qǐng)求封裝成一個(gè)Promise:
// 后臺(tái)post請(qǐng)求 function postRequest(posturl, postdata) { return new Promise((resolve, reject) => { wx.request({ //省略其他屬性 success: function (res) { console.log("at post request: 請(qǐng)求成功") resolve(res.data)//設(shè)置promise成功標(biāo)志 }, fail: function (res) { console.log("at post request: 請(qǐng)求失敗") reject(res.data)//設(shè)置promise失敗標(biāo)志 } }) }); }
這樣封裝以后,我們的網(wǎng)絡(luò)請(qǐng)求會(huì)在success和fail后回調(diào)resolve,這樣可以告訴promise,“hey,我完成我的工作了,你可以進(jìn)行你的then操作了”,這樣就可以用then來簡(jiǎn)化嵌套邏輯。使用promise來完成上面那個(gè)問題的請(qǐng)求將會(huì)是這樣的:
postRequest(posturl,postdata) .then(function(res){ //業(yè)務(wù)邏輯 //調(diào)用下一個(gè)請(qǐng)求 return postRequest(next_posturl,next_postdata); }) .then(function(res){ //業(yè)務(wù)邏輯 //調(diào)用下一個(gè)請(qǐng)求 return postRequest(next_next_posturl,next_next_postdata); }) .then(function(res){ //業(yè)務(wù)邏輯 });
是不是簡(jiǎn)潔的多~
一個(gè)看似簡(jiǎn)單的需求
我們的有一個(gè)很簡(jiǎn)單的需求是需要對(duì)一組數(shù)量不定的圖片做分別上傳(因?yàn)槲⑿畔拗扑詿o法做多上傳),并且在上傳完成以后需要獲取到所有的返回結(jié)果。
那么用我們前面的回調(diào)函數(shù)+then的話,很自然的想到這樣的寫法
postRequest(posturl,postdata) .then(function(res){ //獲取返回res //上傳下一個(gè)圖片 return postRequest(next_posturl,next_postdata); }) .then(function(res){ //獲取返回res //上傳下一個(gè)圖片 return postRequest(next_next_posturl,next_next_postdata); }) .then(function(res){ //獲取返回res });
這樣看起來很簡(jiǎn)單明了,但是我的圖片數(shù)量是不定的,怎么動(dòng)態(tài)的構(gòu)建.then.then.then這樣的鏈?zhǔn)秸{(diào)用呢?經(jīng)過我的研究后發(fā)現(xiàn)可以通過一個(gè)輔助的promise鏈去完成主鏈的鏈?zhǔn)綐?gòu)建。
//多文件上傳 function jabingp_upLoad(uploadurl, files) { return new Promise((resolve, reject) => { //初始化promise鏈 var mergedAjax = Promise.resolve(); var response = []; // 循環(huán)上傳 // 這里一定要使用let來為沒一次循環(huán)構(gòu)建一個(gè)塊級(jí)作用域 // 使用var則需要配合立即執(zhí)行函數(shù) for (let i = 0; i < files.length; i++) { mergedAjax = mergedAjax.then(() => { return jabingp_upLoadSingle(uploadurl, files[i]).then((res) => { response.push(res); }); }); } //當(dāng)前面循環(huán)中所有的then執(zhí)行完畢時(shí)會(huì)執(zhí)行這個(gè)then mergedAjax.then(() => { resolve(response); //設(shè)置這個(gè)函數(shù)的promise對(duì)象為完成狀態(tài)并放入數(shù)據(jù) }); }); }
通過這個(gè)函數(shù),就完成了多個(gè)請(qǐng)求依次執(zhí)行并收集結(jié)果的效果。這個(gè)函數(shù)的重點(diǎn)在于利用另外一個(gè)已經(jīng)處于完成狀態(tài)的promise,不斷的迭代自身,在每次迭代的then內(nèi)部通過return來完成輔助鏈到業(yè)務(wù)鏈的切換。
2019-04-27 更新
使用await/async更加優(yōu)雅地處理異步吧!
在es7標(biāo)準(zhǔn)中,引入了await和async這對(duì)兄弟,它們可以讓我們的異步代碼看起來和同步代碼一樣。讓我們來看看await和async都能做什么吧。
await可以等待一個(gè)promise運(yùn)行到完成狀態(tài)并且獲取結(jié)果,或者等待一個(gè)async修飾的函數(shù)運(yùn)行完成并獲取結(jié)果,但是使用await的時(shí)候,必須在async函數(shù)體內(nèi)部。比如我有這樣一個(gè)網(wǎng)絡(luò)請(qǐng)求:
function postRequest(posturl, postdata) { return new Promise((resolve, reject) => { wx.request({ //省略其他屬性 success: function (res) { console.log("at post request: 請(qǐng)求成功") resolve(res.data)//設(shè)置promise成功標(biāo)志 }, fail: function (res) { console.log("at post request: 請(qǐng)求失敗") reject(res.data)//設(shè)置promise失敗標(biāo)志 } }) }); }
那么如果不使用await,我就需要這樣取得請(qǐng)求結(jié)果
function test(){ postRequest(xxx,xxx).then(function(res){ // 這里面可以讀取請(qǐng)求結(jié)果res了 console.log(res); }); } test();
可以看到,這樣的代碼不太符合常規(guī)邏輯,我們希望函數(shù)作用是返回?cái)?shù)據(jù),這樣更清晰明了,有了await,我們的愿望就可以實(shí)現(xiàn)了。
async function test(){ let res = await postRequest(xxx,xxx); // 下面就可以正常寫對(duì)res的讀取了 console.log(res); } test();
注意我給函數(shù)加上了async,有了async和await,我們就可以像同步代碼一樣使用異步請(qǐng)求了~
那么上面那個(gè)通過復(fù)雜的構(gòu)建鏈完成的需求,通過await實(shí)現(xiàn)將會(huì)變得非常簡(jiǎn)單易懂。
async function jabingp_upLoad(uploadurl, files) { let response = []; // 循環(huán)依次等待上傳結(jié)果 for (let i = 0; i < files.length; i++) { let res = await jabingp_upLoadSingle(uploadurl, files[i]); // 結(jié)果放入數(shù)組 response.push(res); } // 返回結(jié)果 return response ; }
代碼一下子變得簡(jiǎn)潔易懂了,注意調(diào)用的時(shí)候也同樣需要在一個(gè)async函數(shù)內(nèi)部執(zhí)行await。
async function test(){ let response = await jabingp_upLoad(xxx,xxx); console.log(response ); } test();
是不是非常簡(jiǎn)單呢,趕緊在你的異步請(qǐng)求中使用async和await吧~
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Javascript學(xué)習(xí)筆記 delete運(yùn)算符
關(guān)于javascript的delete運(yùn)算符,MDN里有相關(guān)文檔。以下是我的學(xué)習(xí)筆記,更多是要關(guān)注特殊情況的使用和注意點(diǎn)。2011-09-09javaScript實(shí)現(xiàn)滾動(dòng)新聞的方法
這篇文章主要介紹了javaScript實(shí)現(xiàn)滾動(dòng)新聞的方法,涉及javascript實(shí)現(xiàn)頁(yè)面滾動(dòng)的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07JavaScript將對(duì)象數(shù)組按字母順序排序的方法詳解
這篇文章主要介紹了JavaScript如何將對(duì)象數(shù)組按字母順序排序,本文介紹了三種解決方案,if條件語(yǔ)句 + sort(),localeCompare() + sort(),Collator() + sort(),有感興趣的同學(xué)可以跟著小編一起來看看2023-07-07js 實(shí)現(xiàn)在離開頁(yè)面時(shí)提醒未保存的信息(減少用戶重復(fù)操作)
在離開頁(yè)面時(shí)判斷是否有未保存的輸入值,然后進(jìn)行提醒,接下來介紹實(shí)現(xiàn)步驟,感興趣的朋友可以了解下2013-01-01省市選擇的簡(jiǎn)單實(shí)現(xiàn)(基于zepto.js)
下面小編就為大家?guī)硪黄∈羞x擇的簡(jiǎn)單實(shí)現(xiàn)(基于zepto.js)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨想過來看看吧2016-06-06uniapp上傳二進(jìn)制圖片的實(shí)現(xiàn)
本文主要介紹了uniapp上傳二進(jìn)制圖片的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06使用layui實(shí)現(xiàn)樹形結(jié)構(gòu)的方法
今天小編就為大家分享一篇使用layui實(shí)現(xiàn)樹形結(jié)構(gòu)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-09-09如何優(yōu)雅地取消 JavaScript 異步任務(wù)
這篇文章主要介紹了如何優(yōu)雅地取消 JavaScript 異步任務(wù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03