事實上async function只不過是對Promise一個很好的封裝,從es6到es7,而async異步方法確實實現起來 也可讓代碼變得很優雅,下面就由淺到深具體說說其中的原理。
html
promise是es6中實現的一個對象,它接收一個函數做爲參數。這個函數又有兩個參數,分別是 resolve和reject。node
const a = new Promise(function(resolve, reject){
console.log(1)
resolve(3)
reject(5)
console.log(4)
})
console.log('outter')
console.log(a)
複製代碼
結果以下:git
console.log(4)
在最後一行,可是依然比resolve先執行。儘管如此,寫代碼時仍是應該要注意。(再看看上面,出口天然應該放在邏輯的最後一步)。
const a = new Promise(function(resolve, reject){
console.log(1)
setTimeout( () => {
resolve('inner')
})
})
console.log('outter')
console.log(a)
複製代碼
const a = new Promise(function(resolve, reject){
console.log(1) //1
resolve('inner')
})
console.log('outter') //2
a.then(v => {
console.log(v) //4
})
console.log(a) //3
複製代碼
const a = new Promise(function(resolve, reject){
console.log(1)
resolve('inner')
})
const b = a.then(v => {
console.log(v)
})
setTimeout( () => {
console.log(b)
})
複製代碼
首先說明一下,全部的setTimeout(包括setInterval)都默認至少有一個4ms,就算你不寫。而且setTimeout是瀏覽器提供的另外一個線程來實現,而promise則是做爲es6的規範。(若是用node就好解釋了,我更傾向於認爲Promise是相似於nexttick之類的接口。瀏覽器環境下的js並不像node具備多個隊列,只有一個主線程運行隊列,Promise必定會在當前主線程隊列運行完畢的最後一個)。不瞭解的node也無所謂,這裏只須要記住promise必定比setTimeout快!setTimeout有4ms呢!言歸正傳,實質上這裏是幫咱們返回了一個已是resolved狀態的Promise(具體規則見mdn),而且由於咱們並無傳遞參數,所以這裏接收到的參數就是undefined。接着看代碼es6
const a = new Promise(function(resolve, reject){
resolve('inner')
})
const b = a.then(v => {
return new Promise((resolve, reject) => {
resolve(v)
})
})
setTimeout( () => {
console.log(b)
},1000)
複製代碼
a.then(v => {
return new Promise((resolve, reject) => {
resolve(v)
})
}).then(v => {
}).then(v =>{
})
複製代碼
哎喲,then一多,好醜啊。代碼一點也不優雅,是的。這確實是個問題,這才引出了async的解決方案,但還不到談那個的時間。讓咱們先把promise說完。
可能有小夥伴發現了,reject你一直都沒說呢?是的,先說完resolve再說這個,其實我我的理解rejcet爲拋出一個異常,咱們能夠在then中去處理,可是咱們也能夠在catch中處理(我推薦這種,至於爲何,我把兩種寫法列出來你就明白了)。
如今假設有一個業務邏輯,須要判斷以後咱們再決定走哪一個出口。下面第一種是用then的github
const a = new Promise(function(resolve, reject){
if(0) {
resolve('成功了')
} else {
reject('錯誤了')
}
})
a.then( v => {
console.log(v)
return new Promise((resolve, reject) => {
resolve(v)
})
}, e => {
console.log(e)
})
複製代碼
a.then( v => {
console.log(v)
return new Promise((resolve, reject) => {
resolve(v)
})
}).catch(e => {
console.log(e)
})
複製代碼
結果圖如上,我就不貼了。是否是很優雅?(額。。。。單純指的是相對then來講。)
總而言之,resolve,reject。對應兩種狀態,兩種出口,出口中傳遞參數。出口以前都爲同步。出口以後,then,catch都是異步,而且咱們能夠在then和catch中接收以前同步的傳出來的參數。而且要注意的是resolve狀態和reject兩種出口咱們要用不一樣的方式來接收。一種我認爲是成功,一種是異常,異常必需要去捕獲。
說到這裏其實promise也差很少了,再提兩個方法,一個是Promise.race,一個是Promise.all。注意了,這兩個都是類方法。Promise.race方法是將多個 Promise 實例,包裝成一個新的 Promise 實例。數組
const result = Promise.race([a, b, c]);
複製代碼
a,b,c都是promise的實例,這三個實例哪個先結束,就先返回一個。result就變成哪個。舉個場景就明白了。如今咱們須要一張圖片,這個圖片異步加載,可是它是哪張我不關心(只要是給定的三張中的一張),我定了三個異步任務,先返回的那張我放到html上。嗯,就這麼簡單。可是要注意,若是第一個結束的是錯誤的,同樣也是算做跑最快的那個,返回給result。所以外面應該用catch接收一下,同時自行判斷邏輯(可能由於網絡的緣由須要咱們再執行一遍啦仍是啥)
Promise.all。他必需要接收的promise實例所有變爲resolve才返回(返回這些promise實例中resolve中的參數組成的數組),有一個變成reject,它就返回這個reject的參數。直接舉例子。咱們須要異步加載三張圖片,可是我必需要三張所有加載完我一塊兒顯示,我不要一張一張的出來。三張都出來就是resolve,任意一張失敗了很差意思我就都不給你顯示。
剩下的還有一些promise方法我就很少說了,用的也很少,直接看文檔就行了。promise
實際上,async function的使用方法跟普通函數如出一轍,若是你在async function中沒有使用await關鍵字 的話,從某種程度上來講它就是普通函數。。。。先來個代碼壓壓驚。瀏覽器
async function test() {
console.log(1)
const a = await new Promise(function(resolve, reject){
resolve(3)
})
console.log(a)
}
test()
console.log(2)
複製代碼
仍是同樣,代碼說話bash
async function test() {
console.log(1)
const a = await new Promise(function(resolve, reject){
resolve(3)
})
console.log('我是被處理後的:', a)
}
const b = test()
console.log('我是還沒被處理後的:', b)
複製代碼
resolve(3)
,而後咱們在then中接收這個參數,可是如今await直接就幫咱們處理了這個參數,也就是await會處理這個promise對象,再返回裏面的參數。而處理以前,咱們主線程任務必須先運行完。所以這裏咱們打印的b的結果正是一個處於pending狀態的promise對象。其次,async function只要一到await,那麼函數這時候就等同於異步了, (見上面代碼,再看看這段話的開頭)。總而言之就是當咱們運行到await的時候,在這個async function內部在await以後的全部代碼都會 等待await處理完畢結果以後纔會執行,而當await開始處理結果,很差意思,這就不屬於同步的範圍了。(
是否是異步極其優雅的實現方法!!!)
有的同窗就問了,那麼await只能處理promise對象嗎,不是的。見代碼網絡
async function test() {
console.log(1)
const b = await '我經常由於本身不夠優秀而感到恐慌'
console.log(b)
const a = await new Promise(function(resolve, reject){
resolve(3)
})
console.log('我是被處理後的:', a)
}
const b = test()
console.log('我是還沒被處理後的:', b)
複製代碼
async function test() {
const a = await new Promise(function(resolve, reject){
reject(3)
})
}
test()
複製代碼
報錯了。。咋辦呢。。。
async function test() {
try {
const a = await new Promise(function(resolve, reject){
reject('完蛋,我會被捕獲')
})
} catch(e) {
console.log(e)
}
}
test()
複製代碼
很好,下面來個再度進階的,也是我我的以前遇到的一個坑。場景是這樣的,我作了一個很是簡單的爬蟲(puppeteer),主要就是爬取圖片而後下載到本地。 場景中有這樣一段代碼。
srcs.forEach( async(src) => {
await srcImages(srcs[i], config.cat)
console.log(1)
page.waitfor(200)
})
async srcImages(){conole.log(2)//balabalabalabala具體邏輯就略過了}
複製代碼
大概意思就是我爬取了每一個圖片的網址,放在一個數組裏,而後對數組裏面每一個地址都調用一個函數,這個函數負責下載。而且這個函數就是用async包裹的,(還記得async就是把一整個函數用Promise包裹嗎),而後每一次下載完我都等待200ms,避免操做太頻繁把我IP封了。問題來了,小夥伴猜猜輸出????
結果證實,我一開始的想法徹底錯誤,這些1是連續打出來的。嘿嘿嘿,爲何會這樣呢?我來捋一捋,咱們一開始對數組第一項進行操做,遇到了第一個await,很好,後面代碼所有異步等待。關鍵來了,主線任務接着運行,開始操做數組的第二項。。。。。就這樣,把數組所有遍歷完畢以後,咱們再所有一塊兒下載(以前都所有掛在異步等待同步的主線程運行結束呢)。。。。。所有下載完後,咱們打印全部的2。。。。而後咱們再咱們打印全部的1。。。咱們再等待200ms * 數組長度的時間。。 。餓。。。坑人。。。那麼我最後是怎麼解決的?
async function test() {
for(let i = 0; i < srcs.length; i++) {
await srcImages(srcs[i], config.cat)
await page.waitfor(200)
}
}
test()
複製代碼
嘿嘿嘿,仍是用for循環來代替forEach好一些。這裏也給我提了個警鐘,當傳統的那些forEach,map之流遇到async的時候,仍是應該注意一下的,可能會跟預想的邏輯不同哦。
在這篇文章以後,後續的文章我應該都只會發到本身的博客上。每次發的時候應該都會在掘金沸點更新一下。但若是文章較長的話我應該也會先在掘金上更新一下(畢竟圖片多了的話。掘金上外鏈直接生成,而不是存在本地)。
好了,到這裏也就結束了。不瞭解node的小夥伴們能夠撤了。下面貼一個在node中本身實現的promisify方法。
const fs = require('fs');
function promisify(f) {
return function() { //雖然這裏函數沒參數,但運行時確定會有參數哦
let args = Array.prototype.slice.call(arguments)
return new Promise((resolve,reject) => {
args.push((err, result) => {
if(err) {
reject(err)
} else {
resolve(result)
}
})
f.apply(null, args)
})
}
}
readFile = promisify(fs.readFile);
//基礎版
readFile('./app.js').then( data => {
console.log(data.toString())
}).catch(e => {
console.log(e)
})
//進階版! 使用async await
async funtion test() {
try {
const content = await readFile('./app.js')
console.log(content)
} catch(e) {
}
}
複製代碼
回調地獄問題在node中很是明顯,而咱們經過promisify能夠將一個函數轉化爲Promise對象。node中任何一個函數的最後一個回調函數必定是(err, data) => {}
。所以這裏咱們就把其做爲數組的最後一項。若是err咱們就從reject出口 出去,若是成功就從resolve出口出去。而第一步promisify則是有點像是函數柯里化,返回一個函數地址。好了文章到這裏就結束了。