此文只介紹Async/Await與Promise基礎知識與實際用到注意的問題,將經過不少代碼實例進行說明,兩個實例代碼是setDelay
和setDelaySecond
。
tips:本文系原創轉自個人博客異步Promise及Async/Await最完整入門攻略,歡迎前端大神交流,指出問題html
咱們都知道已經有了Promise
的解決方案了,爲何還要ES7提出新的Async/Await標準呢?前端
答案其實也顯而易見:Promise
雖然跳出了異步嵌套的怪圈,用鏈式表達更加清晰,可是咱們也發現若是有大量的異步請求的時候,流程複雜的狀況下,會發現充滿了屏幕的then
,看起來很是吃力,而ES7的Async/Await的出現就是爲了解決這種複雜的狀況。node
首先,咱們必須瞭解Promise
。git
什麼是Promise,不少人應該都知道基礎概念?直接看下面的代碼(全文的例子都是基於setDelaySecond
和setDelay
兩個函數,請務必記住):es6
const setDelay = (millisecond) => { return new Promise((resolve, reject)=>{ if (typeof millisecond != 'number') reject(new Error('參數必須是number類型')); setTimeout(()=> { resolve(`我延遲了${millisecond}毫秒後輸出的`) }, millisecond) }) }
咱們把一個Promise封裝在一個函數裏面同時返回了一個Promise,這樣比較規範。github
能夠看到定義的Promise有兩個參數,resolve
和reject
。web
resolve
:將異步的執行從pending(請求)
變成了resolve(成功返回)
,是個函數執行返回。reject
:顧名思義「拒絕」,就是從請求變成了"失敗",是個函數能夠執行返回一個結果,但咱們這裏推薦你們返回一個錯誤new Error()
。
上述例子,你能夠
reject('返回一個字符串')
,隨便你返回,可是咱們仍是
建議返回一個Error對象,這樣更加清晰是「失敗的」,這樣更規範一點。
咱們經過Promise的原型方法then
拿到咱們的返回值:segmentfault
setDelay(3000) .then((result)=>{ console.log(result) // 輸出「我延遲了2000毫秒後輸出的」 })
輸出下列的值:「我延遲了2000毫秒後輸出的」。數組
若是出錯呢?那就用catch
捕獲:promise
setDelay('我是字符串') .then((result)=>{ console.log(result) // 不進去了 }) .catch((err)=>{ console.log(err) // 輸出錯誤:「參數必須是number類型」 })
是否是很簡單?好,如今我增長一點難度,若是多個Promise
執行會是怎麼樣呢?
咱們在寫一個Promise:
const setDelaySecond = (seconds) => { return new Promise((resolve, reject)=>{ if (typeof seconds != 'number' || seconds > 10) reject(new Error('參數必須是number類型,而且小於等於10')); setTimeout(()=> { console.log(`先是setDelaySeconds函數輸出,延遲了${seconds}秒,一共須要延遲${seconds+2}秒`) resolve(setDelay(2000)) // 這裏依賴上一個Promise }, seconds * 1000) }) }
在下一個須要依賴的resolve
去返回另外一個Promise,會發生什麼呢?咱們執行一下:
setDelaySecond(3).then((result)=>{ console.log(result) }).catch((err)=>{ console.log(err); })
你會發現結果是先執行:「先是setDelaySeconds輸出,延遲了2秒,一共須要延遲5秒」
再執行setDelay
的resolve
:「我延遲了2000毫秒後輸出的」。的確作到了依次執行的目的。
有人說,我不想耦合性這麼高,想先執行setDelay
函數再執行setDelaySecond
,但不想用上面那種寫法,能夠嗎,答案是固然能夠。
先改寫一下setDelaySecond
,拒絕依賴,下降耦合性
const setDelaySecond = (seconds) => { return new Promise((resolve, reject)=>{ if (typeof seconds != 'number' || seconds > 10) reject(new Error('參數必須是number類型,而且小於等於10')); setTimeout(()=> { resolve(`我延遲了${seconds}秒後輸出的,是第二個函數`) }, seconds * 1000) }) }
先執行setDelay
在執行setDelaySecond
,只須要在第一個then
的結果中返回下一個Promise就能夠一直鏈式寫下去了,至關於依次執行:
setDelay(2000) .then((result)=>{ console.log(result) console.log('我進行到第一步的'); return setDelaySecond(3) }) .then((result)=>{ console.log('我進行到第二步的'); console.log(result); }).catch((err)=>{ console.log(err); })
發現確實達到了可喜的鏈式(終於脫離異步嵌套苦海,哭),能夠看到then
的鏈式寫法很是優美。
這裏必定要提到一點:
then
式鏈式寫法的本質實際上是一直往下傳遞返回一個新的Promise,也就是說then在下一步接收的是上一步返回的Promise,理解這個對於後面的細節很是重要!!
那麼並非這麼簡單,then的返回咱們能夠看出有2個參數(都是回調):
Promise
成功resolve
的值。咱們修改上面的代碼:
setDelay(2000) .then((result)=>{ console.log(result) console.log('我進行到第一步的'); return setDelaySecond(20) }) .then((result)=>{ console.log('我進行到第二步的'); console.log(result); }, (_err)=> { console.log('我出錯啦,進到這裏捕獲錯誤,可是不通過catch了'); }) .then((result)=>{ console.log('我仍是繼續執行的!!!!') }) .catch((err)=>{ console.log(err); })
能夠看到輸出結果是:進到了then
的第二個參數(reject)中去了,並且最重要的是!再也不通過catch
了。
那麼咱們把catch挪上去,寫到then
錯誤處理前:
setDelay(2000) .then((result)=>{ console.log(result) console.log('我進行到第一步的'); return setDelaySecond(20) }) .catch((err)=>{ // 挪上去了 console.log(err); // 這裏catch到上一個返回Promise的錯誤 }) .then((result)=>{ console.log('我進行到第二步的'); console.log(result); }, (_err)=> { console.log('我出錯啦,可是因爲catch在我前面,因此錯誤早就被捕獲了,我這沒有錯誤了'); }) .then((result)=>{ console.log('我仍是繼續執行的!!!!') })
能夠看到先通過catch
的捕獲,後面就沒錯誤了。
能夠得出須要注意的:
catch
寫法是針對於整個鏈式寫法的錯誤捕獲的,而then
第二個參數是針對於上一個返回Promise
的。break
, 能夠繼續執行後續操做不受影響。上述已經寫好了關於then裏面三個回調中第二個回調(reject)會與catch衝突的問題,那麼咱們實際寫的時候,參數捕獲的方式基本寫得少,catch的寫法會用到更多。
既然有了不少的Promise,那麼我需不須要寫不少catch呢?
答案固然是:不須要!,哪有那麼麻煩的寫法,只須要在末尾catch
一下就能夠了,由於鏈式寫法的錯誤處理具備「冒泡」特性,鏈式中任何一個環節出問題,都會被catch
到,同時在某個環節後面的代碼就不會執行了。
既然說到這裏,咱們把catch
移到第一個鏈式的返回裏面會發生什麼事呢?看下面代碼:
setDelay('2000') .then((result)=>{ console.log('第一步完成了'); console.log(result) return setDelaySecond(3) }) .catch((err)=>{ // 這裏移到第一個鏈式去,發現上面的不執行了,下面的繼續執行 console.log(err); }) .then((result)=>{ console.log('第二步完成了'); console.log(result); })
驚喜的發現,鏈式繼續走下去了!!輸出以下(undefined是由於上一個then沒有返回一個Promise):
重點來了!敲黑板!!鏈式中的catch
並非終點!!catch完若是還有then還會繼續往下走!不信的話能夠把第一個catch
在最後面的那個例子後面再加幾個then
,你會發現並不會跳出鏈式執行。
若是順序執行setDelay,setDelay1,setDelaySecond
,按照上述的邏輯,流程圖能夠歸納以下:
catch
只是捕獲錯誤的一個鏈式表達,並非break!
因此,catch放的位置也頗有講究,通常放在一些重要的、必須catch的程序的最後。**這些重要的程序中間一旦出現錯誤,會立刻跳過其餘後續程序的操做直接執行到最近的catch代碼塊,但不影響catch後續的操做!!!!
到這就不得不體一個ES2018標準新引入的Promise的finally
,表示在catch後必須確定會默認執行的的操做。這裏很少展開,細節能夠參考:Promise的finally
其實很簡單,用Promise
的原型方法resolve
便可:
setDelay(2000).then((result)=>{ console.log('第一步完成了'); console.log(result); let message = '這是我本身想處理的值'; return Promise.resolve(message) // 這裏返回我想在下一階段處理的值 }) .then((result)=>{ console.log('第二步完成了'); console.log(result); // 這裏拿到上一階段的返回值 //return Promise.resolve('這裏能夠繼續返回') }) .catch((err)=>{ console.log(err); })
不一樣於通常的function
的break
的方式,若是你是這樣的操做:func().then().then().then().catch()
的方式,你想在第一個then
就跳出鏈式,後面的不想執行了,不一樣於通常的break;return null;return false
等操做,能夠說,如何中止Promise鏈,是一大難點,是整個Promise最複雜的地方。
1.用鏈式的思惟想,咱們拒絕掉某一鏈,那麼不就是至關於直接跳到了catch模塊嗎?
咱們是否是能夠直接「拒絕「掉達到中止的目的?
setDelay(2000) .then((result)=>{ console.log(result) console.log('我進行到第一步的'); return setDelaySecond(1) }) .then((result)=>{ console.log('我進行到第二步的'); console.log(result); console.log('我主動跳出循環了'); return Promise.reject('跳出循環的信息') // 這裏返回一個reject,主動跳出循環了 }) .then((result)=>{ console.log('我不執行'); }) .catch((mes)=>{ console.dir(mes) console.log('我跳出了'); })
可是很容易看到缺點:有時候你並不肯定是由於錯誤跳出的,仍是主動跳出的,因此咱們能夠加一個標誌位:
return Promise.reject({ isNotErrorExpection: true // 返回的地方加一個標誌位,判斷是不是錯誤類型,若是不是,那麼說明能夠是主動跳出循環的 })
或者根據上述的代碼判斷catch的地方輸出的類型是否是屬於錯誤對象的,是的話說明是錯誤,不是的話說明是主動跳出的,你能夠本身選擇(這就是爲何要統一錯誤reject的時候輸出new Error('錯誤信息')的緣由,規範!)
固然你也能夠直接拋出一個錯誤跳出:
throw new Error('錯誤信息') // 直接跳出,那就不能用判斷是否爲錯誤對象的方法進行判斷了
2.那有時候咱們有這個需求:catch是放在中間(不是末尾),而同時咱們又不想執行catch後面的代碼,也就是鏈式的絕對停止,應該怎麼辦?
咱們看這段代碼:
setDelay(2000) .then((result)=>{ console.log(result) console.log('我進行到第一步的'); return setDelaySecond(1) }) .then((result)=>{ console.log('我進行到第二步的'); console.log(result); console.log('我主動跳出循環了'); return Promise.reject('跳出循環的信息') // 這裏直接調用Promise原型方法返回一個reject,主動跳出循環了 }) .then((result)=>{ console.log('我不執行'); }) .catch((mes)=>{ console.dir(mes) console.log('我跳出了'); }) .then((res)=>{ console.log('我不想執行,可是卻執行了'); // 問題在這,上述的終止方法治標不治本。 })
這時候最後一步then
仍是執行了,整條鏈都其實沒有本質上的跳出,那應該怎麼辦呢?
敲黑板!!重點來了!咱們看Promise/A+規範能夠知道:
A promise must be in one of three states: pending, fulfilled, or rejected.
Promise實際上是有三種狀態的:pending,resolve,rejected,那麼咱們一直在討論resolve和rejected
這2個狀態,是否是忽視了pending
這個狀態呢?pending狀態顧名思義就是請求中的狀態,成功請求就是resolve,失敗就是reject,其實他就是個中間過渡狀態。
而咱們上面討論過了,then
的下一層級其實獲得的是上一層級返回的Promise對象,也就是說原Promise對象與新對象狀態保持一致。那麼重點來了,若是你想在這一層級進行終止,是否是直接讓它永遠都pending
下去,那麼後續的操做不就沒了嗎?是否是就達到這個目的了??以爲有疑問的能夠參考Promise/A+規範。
咱們直接看代碼:
setDelay(2000) .then((result)=>{ console.log(result) console.log('我進行到第一步的'); return setDelaySecond(1) }) .then((result)=>{ console.log(result); console.log('我主動跳出循環了'); // return Promise.reject('跳出循環的信息') // 重點在這 return new Promise(()=>{console.log('後續的不會執行')}) // 這裏返回的一個新的Promise,沒有resolve和reject,那麼會一直處於pending狀態,由於沒返回啊,那麼這種狀態就一直保持着,中斷了這個Promise }) .then((result)=>{ console.log('我不執行'); }) .catch((mes)=>{ console.dir(mes) console.log('我跳出了'); }) .then((res)=>{ console.log('我也不會執行') })
這樣就解決了上述,錯誤跳出而致使沒法徹底終止Promise鏈的問題。
可是!隨之而來也有一個問題,那就是可能會致使潛在的內存泄漏,由於咱們知道這個一直處於pending狀態下的Promise會一直處於被掛起的狀態,而咱們具體不知道瀏覽器的機制細節也不清楚,通常的網頁沒有關係,但大量的複雜的這種pending狀態勢必會致使內存泄漏,具體的沒有測試過,後續可能會跟進測試(nodeJS或webapp裏面不推薦這樣),而我經過查詢也難以找到答案,這篇文章能夠推薦看一下:從如何停掉 Promise 鏈提及。可能對你有幫助在此種狀況下如何作。
固然通常狀況下是不會存在泄漏,只是有這種風險,沒法取消Promise一直是它的痛點。而上述兩個奇妙的取消方法要具體情形具體使用。
其實這幾個方法就簡單了,就是一個簡寫串聯全部你須要的Promise
執行,具體能夠參照阮一峯的ES6Promise.all教程。
我這上一個代碼例子
Promise.all([setDelay(1000), setDelaySecond(1)]).then(result=>{ console.log(result); }) .catch(err=>{ console.log(err); }) // 輸出["我延遲了1000毫秒後輸出的", "我延遲了1秒後輸出的,注意單位是秒"]
輸出的是一個數組,至關於把all
方法裏面的Promise
並行執行,注意是並行。
至關於兩個Promise同時開始執行,同時返回值,並非先執行第一個再執行第二個,若是你想串行執行,請參考我後面寫的循環Promise循環串行(第4.2小節)。
而後把resolve的值保存在數組中輸出。相似的還有Promise.race這裏就很少贅述了。
什麼是async/await
呢?能夠總結爲一句話:async/await是一對好基友,缺一不可,他們的出生是爲Promise服務的。能夠說async/await是Promise的爸爸,進化版。爲何這麼說呢?且聽我細細道來。
爲何要有async/await
存在呢?
前文已經說過了,爲了解決大量複雜不易讀的Promise異步的問題,纔出現的改良版。
這兩個基友必須同時出現,缺一不可,那麼先說一下Async
:
async function process() { }
上面能夠看出,async
必須聲明的是一個function,不要去聲明別的,要是那樣await
就不理你了(報錯)。
這樣聲明也是錯的!
const async demo = function () {} // 錯誤
必須緊跟着function
。接下來講一下它的兄弟await
。
上面說到必須是個函數(function),那麼await
就必須是在這個async
聲明的函數內部使用,不然就會報錯。
就算你這樣寫,也是錯的。
let data = 'data' demo = async function () { const test = function () { await data } }
必須是直系(做用域鏈不能隔代),這樣會報錯:Uncaught SyntaxError: await is only valid in async function
。
講完了基本規範,咱們接下去說一下他們的本質。
敲黑板!!!很重要!async聲明的函數的返回本質上是一個Promise。
什麼意思呢?就是說你只要聲明瞭這個函數是async
,那麼內部無論你怎麼處理,它的返回確定是個Promise。
看下列例子:
(async function () { return '我是Promise' })() // 返回是Promise //Promise {<resolved>: "我是Promise"}
你會發現返回是這個:Promise {<resolved>: "我是Promise"}
。
自動解析成Promise.resolve('我是Promise');
等同於:
(async function () { return Promise.resolve('我是Promise'); })()
因此你想像通常function
的返回那樣,拿到返回值,原來的思惟要改改了!你能夠這樣拿到返回值:
const demo = async function () { return Promise.resolve('我是Promise'); // 等同於 return '我是Promise' // 等同於 return new Promise((resolve,reject)=>{ resolve('我是Promise') }) } demo.then(result=>{ console.log(result) // 這裏拿到返回值 })
上述三種寫法都行,要看註釋細節都寫在裏面了!!像對待Promise同樣去對待async的返回值!!!
好的接下去咱們看await
的幹嗎用的.
await的本質是能夠提供等同於」同步效果「的等待異步返回能力的語法糖。
這一句咋一看很彆扭,好的不急,咱們從例子開始看:
const demo = async ()=>{ let result = await new Promise((resolve, reject) => { setTimeout(()=>{ resolve('我延遲了一秒') }, 1000) }); console.log('我因爲上面的程序還沒執行完,先不執行「等待一會」'); } // demo的返回當作Promise demo().then(result=>{ console.log('輸出',result); })
await顧名思義就是等待一會,只要await
聲明的函數尚未返回,那麼下面的程序是不會去執行的!!!。這就是字面意義的等待一會(等待返回再去執行)。
那麼你到這測試一下,你會發現輸出是這個:輸出 undefined
。這是爲何呢?這也是我想強調的一個地方!!!
你在demo
函數裏面都沒聲明返回,哪來的then
?因此正確寫法是這樣:
const demo = async ()=>{ let result = await new Promise((resolve, reject) => { setTimeout(()=>{ resolve('我延遲了一秒') }, 1000) }); console.log('我因爲上面的程序還沒執行完,先不執行「等待一會」'); return result; } // demo的返回當作Promise demo().then(result=>{ console.log('輸出',result); // 輸出 我延遲了一秒 })
我推薦的寫法是帶上then
,規範一點,固然你沒有返回也是沒問題的,demo
會照常執行。下面這種寫法是不帶返回值的寫法:
const demo = async ()=>{ let result = await new Promise((resolve, reject) => { setTimeout(()=>{ resolve('我延遲了一秒') }, 1000) }); console.log('我因爲上面的程序還沒執行完,先不執行「等待一會」'); } demo();
因此能夠發現,只要你用await聲明的異步返回,是必須「等待」到有返回值的時候,代碼才繼續執行下去。
那事實是這樣嗎?你能夠跑一下這段代碼:
const demo = async ()=>{ let result = await setTimeout(()=>{ console.log('我延遲了一秒'); }, 1000) console.log('我因爲上面的程序還沒執行完,先不執行「等待一會」'); return result } demo().then(result=>{ console.log('輸出',result); })
你會發現,輸出是這樣的:
我因爲上面的程序還沒執行完,先不執行「等待一會」 輸出 1 我延遲了一秒
奇怪,並無await啊?setTimeout
是異步啊,問題在哪?問題就在於setTimeout
這是個異步,可是不是Promise
!起不到「等待一會」的做用。
因此更準確的說法應該是用await聲明的Promise異步返回,必須「等待」到有返回值的時候,代碼才繼續執行下去。
固然這種等待的效果只存在於「異步」的狀況,await能夠用於聲明通常狀況下的傳值嗎?
事實是固然能夠:
const demo = async ()=>{ let message = '我是聲明值' let result = await message; console.log(result); console.log('我因爲上面的程序還沒執行完,先不執行「等待一會」'); return result } demo().then(result=>{ console.log('輸出',result); })
輸出:
我是聲明值 我因爲上面的程序還沒執行完,先不執行「等待一會」 輸出 我是聲明值
這裏只要注意一點:then
的執行老是最後的。
如今咱們看一下實戰:
const setDelay = (millisecond) => { return new Promise((resolve, reject)=>{ if (typeof millisecond != 'number') reject(new Error('參數必須是number類型')); setTimeout(()=> { resolve(`我延遲了${millisecond}毫秒後輸出的`) }, millisecond) }) } const setDelaySecond = (seconds) => { return new Promise((resolve, reject)=>{ if (typeof seconds != 'number' || seconds > 10) reject(new Error('參數必須是number類型,而且小於等於10')); setTimeout(()=> { resolve(`我延遲了${seconds}秒後輸出的,注意單位是秒`) }, seconds * 1000) }) }
好比上面兩個延時函數(寫在上面),好比我想先延時1秒,在延遲2秒,再延時1秒,最後輸出「完成」,這個過程,若是用then
的寫法,大概是這樣(嵌套地獄寫法出門右拐不送):
setDelay(1000) .then(result=>{ console.log(result); return setDelaySecond(2) }) .then(result=>{ console.log(result); return setDelay(1000) }) .then(result=>{ console.log(result); console.log('完成') }) .catch(err=>{ console.log(err); })
咋一看是否是挺繁瑣的?若是邏輯多了估計看得更累,如今咱們來試一下async/await
(async ()=>{ const result = await setDelay(1000); console.log(result); console.log(await setDelaySecond(2)); console.log(await setDelay(1000)); console.log('完成了'); })()
看!是否是沒有冗餘的長長的鏈式代碼,語義化也很是清楚,很是舒服,那麼你看到這裏,必定還發現了,上面的catch
咱們是否是沒有在async中實現?接下去咱們就分析一下async/await如何處理錯誤?
由於async函數返回的是一個Promise,因此咱們能夠在外面catch
住錯誤。
const demo = async ()=>{ const result = await setDelay(1000); console.log(result); console.log(await setDelaySecond(2)); console.log(await setDelay(1000)); console.log('完成了'); } demo().catch(err=>{ console.log(err); })
在async函數的catch
中捕獲錯誤,當作一個Pormise處理,同時你不想用這種方法,可使用try...catch
語句:
(async ()=>{ try{ const result = await setDelay(1000); console.log(result); console.log(await setDelaySecond(2)); console.log(await setDelay(1000)); console.log('完成了'); } catch (e) { console.log(e); // 這裏捕獲錯誤 } })()
固然這時候你就不須要在外面catch
了。
一般咱們的try...catch
數量不會太多,幾個最多了,若是太多了,說明你的代碼確定須要重構了,必定沒有寫得很是好。還有一點就是try...catch一般只用在須要的時候,有時候不須要catch錯誤的地方就能夠不寫。
有人會問了,我try...catch
好像只能包裹代碼塊,若是我須要拆分開分別處理,不想由於一個的錯誤就整個process都crash掉了,那麼難道我要寫一堆try...catch
嗎?我就是彆扭,我就是不想寫try...catch
怎嘛辦?下面有一種很好的解決方案,僅供參考:
咱們知道await後面跟着的確定是一個Promise
那是否是能夠這樣寫?
(async ()=>{ const result = await setDelay(1000).catch(err=>{ console.log(err) }); console.log(result); const result1 = await setDelaySecond(12).catch(err=>{ console.log(err) }) console.log(result1); console.log(await setDelay(1000)); console.log('完成了'); })()
這樣輸出:
我延遲了1000毫秒後輸出的 Error: 參數必須是number類型,而且小於等於10 at Promise (test4.html:19) at new Promise (<anonymous>) at setDelaySecond (test4.html:18) at test4.html:56 undefined 我延遲了1000毫秒後輸出的 完成了
是否是就算有錯誤,也不會影響後續的操做,是否是很棒?固然不是,你說這代碼也忒醜了吧,亂七八糟的,寫得彆扭await又跟着catch。那麼咱們能夠改進一下,封裝一下提取錯誤的代碼函數:
// to function function to(promise) { return promise.then(data => { return [null, data]; }) .catch(err => [err]); // es6的返回寫法 }
返回的是一個數組,第一個是錯誤,第二個是異步結果,使用以下:
(async ()=>{ // es6的寫法,返回一個數組(你能夠改回es5的寫法以爲不習慣的話),第一個是錯誤信息,第二個是then的異步返回數據,這裏要注意一下重複變量聲明可能致使問題(這裏舉例是全局,若是用let,const,請換變量名)。 [err, result] = await to(setDelay(1000)) // 若是err存在就是有錯,不想繼續執行就拋出錯誤 if (err) throw new Error('出現錯誤,同時我不想執行了'); console.log(result); [err, result1] = await to(setDelaySecond(12)) // 還想執行就不要拋出錯誤 if (err) console.log('出現錯誤,同時我想繼續執行', err); console.log(result1); console.log(await setDelay(1000)); console.log('完成了'); })()
首先咱們要明確的是,Promise
自己是沒法停止的,Promise
自己只是一個狀態機,存儲三個狀態(pending,resolved,rejected),一旦發出請求了,必須閉環,沒法取消,以前處於pending狀態只是一個掛起請求的狀態,並非取消,通常不會讓這種狀況發生,只是用來臨時停止鏈式的進行。
中斷(終止)的本質在鏈式中只是掛起,並非本質的取消Promise
請求,那樣是作不到的,Promise
也沒有cancel
的狀態。
不一樣於Promise
的鏈式寫法,寫在async/await中想要中斷程序就很簡單了,由於語義化很是明顯,其實就和通常的function
寫法同樣,想要中斷的時候,直接return
一個值就行,null
,空,false
都是能夠的。看例子:
let count = 6; const demo = async ()=>{ const result = await setDelay(1000); console.log(result); const result1 = await setDelaySecond(count); console.log(result1); if (count > 5) { return '我退出了,下面的不進行了'; // return; // return false; // 這些寫法均可以 // return null; } console.log(await setDelay(1000)); console.log('完成了'); }; demo().then(result=>{ console.log(result); }) .catch(err=>{ console.log(err); })
實質就是直接return
返回了一個Promise
,至關於return Promise.resolve('我退出了下面不進行了')
,固然你也能夠返回一個「拒絕」:return Promise.reject(new Error('拒絕'))
那麼就會進到錯誤信息裏去。
咱們常常會使用上述兩種寫法,也可能混用,有時候會遇到一些狀況,這邊舉例子說明:
並行的不用多說,很簡單,直接循環發出請求就能夠或者用Promise.all
。若是咱們須要串行循環一個請求,那麼應該怎麼作呢?
咱們須要實現一個依次分別延遲1秒輸出值,一共5秒的程序,首先是Promise的循環,這個循環就相對來講比較麻煩:
先不說循環,咱們先舉一個錯誤的例子,如今有一個延遲函數
const setDelay = (millisecond) => { return new Promise((resolve, reject)=>{ if (typeof millisecond != 'number') reject(new Error('參數必須是number類型')); setTimeout(()=> { resolve(`我延遲了${millisecond}毫秒後輸出的`) }, millisecond) }) }
咱們想作到:「循環串行執行延遲一秒的Promise函數」,指望的結果應該是:隔一秒輸出我延遲了1000毫秒後輸出的
,一共通過循環3次。咱們想固然地寫出下列的鏈式寫法:
arr = [setDelay(1000), setDelay(1000), setDelay(1000)] arr[0] .then(result=>{ console.log(result) return arr[1] }) .then(result=>{ console.log(result) return arr[2] }) .then(result=>{ console.log(result) })
可是很不幸,你發現輸出是並行的!!!也就是說一秒鐘一次性輸出了3個值!。那麼這是什麼狀況呢?其實很簡單。。。就是你把setDelay(1000)
這個直接添加到數組的時候,其實就已經執行了,注意你的執行語句(1000)
這實際上是基礎,是語言的特性,不少粗心的人(或者是沒有好好學習JS的人)會覺得這樣就把函數添加到數組裏面了,卻不知函數已經執行過一次了。
那麼這樣致使的後果是什麼呢?也就是說數組裏面保存的每一個Promise
狀態都是resolve
完成的狀態了,那麼你後面鏈式調用直接return arr[1]
其實沒有去請求,只是當即返回了一個resolve的狀態。因此你會發現程序是至關於並行的,沒有依次順序調用。
那麼解決方案是什麼呢?直接函數名存儲函數的方式(不執行Promise)來達到目的
咱們這樣改一下程序:
arr = [setDelay, setDelay, setDelay] arr[0](1000) .then(result=>{ console.log(result) return arr[1](1000) }) .then(result=>{ console.log(result) return arr[2](1000) }) .then(result=>{ console.log(result) })
上述至關於把Promise
預先存儲在一個數組中,在你須要調用的時候,再去執行。固然你也能夠用閉包的方式存儲起來,須要調用的時候再執行。
上述寫法是不優雅的,次數一多就GG了,爲何要提一下上面的then
,其實就是爲了後面的for
循環作鋪墊。
上面的程序根據規律改寫一下:
arr = [setDelay, setDelay, setDelay] var temp temp = arr[0](1000) for (let i = 1; i <= arr.length; i++) { if (i == arr.length) { temp.then(result=>{ console.log('完成了'); }) break; } temp = temp.then((result)=>{ console.log(result); return arr[i-1](1000) }); }
錯誤處理能夠在for循環中套入try...catch
,或者在你每一個循環點進行.then().catch()
、都是可行的。若是你想提取成公共方法,能夠再改寫一下,利用遞歸的方式:
首先你須要閉包你的Promise
程序
function timeout(millisecond) { return ()=> { return setDelay(millisecond); } }
若是不閉包會致使什麼後果呢?不閉包的話,你傳入的參數值後,你的Promise會立刻執行,致使狀態改變,若是用閉包實現的話,你的Promise會一直保存着,等到你須要調用的時候再使用。並且最大的優勢是能夠預先傳入你須要的參數。
改寫數組:
arr = [timeout(2000), timeout(1000), timeout(1000)]
提取方法,Promise
數組做爲參數傳入:
const syncPromise = function (arr) { const _syncLoop = function (count) { if (count === arr.length - 1) { // 是最後一個就直接return return arr[count]() } return arr[count]().then((result)=>{ console.log(result); return _syncLoop(count+1) // 遞歸調用數組下標 }); } return _syncLoop(0); }
使用:
syncPromise(arr).then(result=>{ console.log(result); console.log('完成了'); }) // 或者 添加到Promise類中方法 Promise.syncAll = function syncAll(){ return syncPromise }// 之後能夠直接使用 Promise.syncAll(arr).then(result=>{ console.log(result); console.log('完成了'); })
還有大神總結了一個reduce
的寫法,其實就是一個迭代數組的過程:
const p = arr.reduce((total, current)=>{ return total.then((result)=>{ console.log(result); return current() }) }, Promise.resolve('程序開始')) p.then((result)=>{ console.log('結束了', result); })
都是可行的,在Promise
的循環領域。
如今就來介紹一下牛逼的async/await實戰,上述的代碼你是否是要看吐了,的確,我也以爲好麻煩啊,那麼若是用async/await
能有什麼改進嗎?這就是它出現的意義:
模擬上述代碼的循環:
(async ()=>{ arr = [timeout(2000), timeout(1000), timeout(1000)] for (var i=0; i < arr.length; i++) { result = await arr[i](); console.log(result); } })()
。。。這就完了?是的。。。就完了,是否是特別方便!!!!語義化也很是明顯!!這裏爲了保持與上面風格一致,沒有加入錯誤處理,因此實戰的時候記得加入你的try...catch
語句來捕獲錯誤。
一直想總結一下Promise
和async/await
,不少地方可能總結得不夠,已經盡力擴大篇幅了,後續有新的知識點和總結點可能會更新(未完待續),可是入門這個基本夠用了。
咱們常說什麼async/await
的出現淘汰了Promise,能夠說是大錯特錯,偏偏相反,正由於有了Promise,纔有了改良版的async/await
,從上面分析就能夠看出,二者是相輔相成的,缺一不可。
想學好async/await
必須先精通Promise
,二者密不可分,有不一樣意見和改進的歡迎指導!
前端小白,你們互相交流,peace!