本文首發於政採雲前端團隊博客:編寫高質量可維護的代碼——異步優化javascript
https://www.zoo.team/article/asynchronization-optimizing
前言
在如今前端開發中,異步操做的頻次已經愈來愈高了,特別對於數據接口請求和定時器的使用,使得咱們不得不關注異步在業務中碰到的場景,以及對異步的優化。錯誤的異步處理可能會帶來不少問題,諸如頁面渲染、重複加載等問題。html
下面咱們就先簡單的從 JavaScript 中有大體的哪幾種異步類型爲切入點,而後再列舉一些業務中咱們會碰到的場景來逐個分析下,咱們該如何解決。前端
異步實現種類
首先關於異步實現的方式上大體有以下幾種:java
callback
callback 即回調函數。這傢伙出現很早很早了,他實際上是處理異步的基本方法。而且回調的概念不僅僅出如今 JavaScript,你也會在 Java 或者 C# 等後端語言中也能找到他的影子。node
回調函數簡單的說其實就是給另一個寄主函數做爲傳參的函數。在寄主函數執行完成或者執行到特定階段以後觸發調用回調函數並執行,而後把執行結果再返回給寄主函數的過程。web
好比咱們熟悉的 setTimeout 或者 React 中的 setState 的第二個方法都是以回調函數方式去解決異步的實現。編程
setTimeout(() => {
//等待0.2s以後再作具體的業務操做
this.doSomething();
}, 200);
this.setState({
count: res.count,
}, () => {
//在更新完count以後再作具體的業務操做
this.doSomething();
});
Promise
Promise 是個好東西,有了它以後咱們能夠對異步進行不少操做,而且能夠把異步以鏈式的方式進行操做。json
其實在 JQuery 中的 deferred 和它就有點像,都是採用回調函數的解決方案,均可以作鏈式調用,可是在 Promise 中增長了錯誤的 catch 方法能夠更加方便的處理異常場景,而且它內置狀態(resolve, reject,pending),狀態只能由 pending 變爲另外兩種的其中一種,且改變後不可逆也不可再度修改。後端
let promise = new Promise((resolve, reject) => {
reject("對不起,你不是個人菜");
});
promise.then((data) => {
console.log('第一次success' + data);
return '第一次success' + data
},(error) => {
console.log(error) }
).then((data2) => {
console.log('第二次success' + data2);
},(error2) => {
console.log(error2) }
).catch((e) => {
console.log('抓到錯誤啦' + e);
});
await/async
await/async 實際上是 Promise 的一種升級版本,使用 await/async 調用異步的時候是從上到下,順序執行,就像在寫同步代碼同樣,這更加的符合咱們編寫代碼的習慣和思惟邏輯,因此容易理解。總體代碼邏輯也會更加的清晰。promise
async function asyncDemoFn() {
const data1 = await getData1();
const data2 = await getData2(data1);
const data3 = await getData3(data2);
console.log(data3)
}
await asyncDemoFn()
generator
generator 中文名叫構造器,是 ES6 中的一個新東西,我相信不少人在現實的代碼中不多能接觸到它,因此它相對而言對你們來講仍是比較晦澀,可是這傢伙仍是很強的,簡單來講它能控制異步調用,而且實際上是一個狀態機。
function* foo() {
for (let i = 1; i <= 3; i++) {
let x = yield `等我一下唄,i = ${i}`;
console.log(x);
}
}
setTimeout(() => {
console.log('終於輪到我了');
}, 1);
var a = foo();
console.log(a); // foo {<closed>}
var b = a.next();
console.log(b); // {value: "等我一下唄,i = 1", done: false}
var c = a.next();
console.log(c); // {value: "等我一下唄,i = 2", done: false}
var d = a.next();
console.log(d); // {value: "等我一下唄,i = 3", done: false}
var e = a.next();
console.log(e); // {value: undefined, done: true}
// 終於輪到我了
上面代碼的函數 foo 是一個協程,它的厲害的地方就是 yield 命令。它表示執行到此處,執行權將交給其餘協程。也就是說,yield 命令是異步兩個階段的分界線。
協程遇到 yield 命令就暫停,等到執行權返回,再從暫停的地方繼續日後執行。它的最大優勢,就是代碼的寫法很是像同步操做,若是去除 yield 命令,簡直如出一轍。
再來個有點貼近點場景方式來使用下 generator。好比如今在頁面中咱們須要自動的執行 checkAuth 和 checkAddress 檢查,咱們就用 generator 的方式去實現自動檢查上述兩異步檢查。
const checkAuth = () => {
return new Promise((resolve)=>{
setTimeout(()=>{
resolve('checkAuth1')
},1000)
})
}
const checkAddress = () => {
return new Promise((resolve)=>{
setTimeout(()=>{
resolve('checkAddress2')
},2000)
})
}
var steps = [checkAuth,checkAddress]
function* foo(checkList) {
for (let i = 0; i < checkList.length; i++) {
let x = yield checkList[i]();
console.log(x);
}
}
var stepsGen = foo(steps)
var run = async (gen)=>{
var isFinnish = false
do{
const {done,value} = gen.next()
console.log('done:',done)
console.log('value:',value)
const result = await value
console.log('result:',result)
isFinnish = done
}while(!isFinnish)
console.log('isFinnish:',isFinnish)
}
run(stepsGen)
種類對比
-
從時間維度從早到晚:callback,Promise,generator,await/async -
await/async 是目前對於異步的終極形式 -
callback 讓咱們有了基本的方式去處理異步狀況,Promise 告別了 callback 的回調地獄而且增長 resolve,reject 和 catch 等方法讓咱們能處理不一樣的狀況,generator 增長了對於異步的可操做性,相似一個狀態機可暫時停住多個異步的執行,而後在合適的時候繼續執行剩餘的異步調用,await/async 讓異步調用更加語義化,而且自動執行異步
異步業務中碰到的場景
回調地獄
在使用回調函數的時候咱們可能會有這樣的場景,B 須要在 A 的返回以後再繼續調用,因此在這樣有前後關係的時候就存在了一個叫回調地獄的問題了。
getData1().then((resData1) => {
getData2(resData1).then((resData2) => {
getData3(resData2).then((resData3)=>{
console.log('resData3:', resData3)
})
});
});
碰到這樣的狀況咱們能夠試着用 await/async 方式去解這種有多個深層嵌套的問題。
async function asyncDemoFn2() {
const resData1 = await getData1();
const resData2 = await getData2(resData1);
const resData3 = await getData3(resData2);
console.log(resData3)
}
await asyncDemoFn2()
異步循環
在業務中咱們最最常常碰到的就是其實仍是存在多個異步調用的順序問題,大體上能夠分爲以下幾種:
並行執行
在並行執行的時候,咱們能夠直接使用 Promise 的 all 方法
Promise.all([getData1(),getData2(),getData3()]).then(res={
console.log('res:',res)
})
順序執行
在順序執行中,咱們能夠有以下的兩種方式去作
-
使用 async/await 配合 for
const sources = [getData1,getData2,getData3]
async function promiseQueue() {
console.log('開始');
for (let targetSource in sources) {
await targetSource();
}
console.log('完成');
};
promiseQueue()
-
使用 async/await 配合 while
// getData1,getData2,getData3 都爲 promise 對象
const sources = [getData1,getData2,getData3]
async function promiseQueue() {
let index = 0
console.log('開始');
while(index >=0 && index < sources.length){
await targetSource();
index++
}
console.log('完成');
};
promiseQueue()
-
使用 async/await 配合 reduce
// getData1,getData2,getData3 都爲 promise 對象
const sources = [getData1,getData2,getData3]
sources.reduce(async (previousValue, currentValue)=>{
await previousValue
return currentValue()
},Promise.resolve())
-
使用遞歸
const sources = [getData1,getData2,getData3]
function promiseQueue(list , index = 0) {
const len = list.length
console.log('開始');
if(index >= 0 && index < len){
list[index]().then(()=>{
promiseQueue(list, index+1)
})
}
console.log('完成');
}
promiseQueue(sources)
結尾
今天只是關於異步的普通使用場景的討論,而且作了些簡單的例子。其實關於異步的使用還有不少不少複雜的使用場景。更多的奇思妙想正等着你。
參考文獻
JS 異步編程六種方案:(https://juejin.im/post/6844903760280420366#heading-12)
Async/Await 替代 Promise 的 6 個理由:(https://blog.fundebug.com/2017/04/04/nodejs-async-await/)
Javascript 異步編程的 4 種方法:(http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html)
看完兩件事
若是你以爲這篇內容對你挺有啓發,我想邀請你幫我兩件小事
1.點個「在看」,讓更多人也能看到這篇內容(點了「在看」,bug -1 😊)
招賢納士
政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 40 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com
本文分享自微信公衆號 - 政採雲前端團隊(Zoo-Team)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。