async & await 再次學(xué)習(xí).
我從簡述某博主的一片博客中看到了一到關(guān)于 async/await
的面試題.
代碼如下:
async function async1() {
console.log( 'async1 start');
await async2();
console.log( 'async1 end');
}
async function async2() {
console.log( 'async2');
}
console.log( 'script start');
setTimeout(function() {
console.log( 'setTimeout');
}, 0)
async1();
new Promise (function ( resolve ) {
console.log( 'promise1');
resolve();
}).then(function() {
console.log( 'promise2');
})
console.log( 'script end');
問題就是:請正確的說出打印順序(在瀏覽器環(huán)境中)?
然后,我就將這段代碼,帖到了瀏覽器中,運(yùn)行了一下.
打印結(jié)果是:
// 瀏覽器環(huán)境執(zhí)行順序
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
當(dāng)然我是一臉懵逼的.
完全不清楚,打印結(jié)果為什么會是這個樣子.
我僅僅只是知道 async/await
是 ES7
推出來的語法.
一般搭配 Promise
來使用.
可以讓我們從同步代碼的方式,來書寫異步代碼.
比如下面這個例子.
場景:我需要按順序的將1.txt和2.txt的內(nèi)容讀取出來,然后拼接成一個字符串.用async/await & promise來做.
1.txt
百度首頁的地址是:
2.txt
我希望打印結(jié)果是:
百度首頁的地址是:https://www.baidu.com
而不是:
https://www.baidu.com百度首頁的地址是:
所以,讀取兩個文件的先后順序就成了關(guān)鍵.
我們可以利用 async/await & Promise
來以同步的方式書寫異步代碼.
讓代碼先讀 1.txt,然后在 2.txt
const fs = require('fs')
const path = require('path')
const FILE_ENCODING = 'utf-8'
const readFilePromiseCreate = (filePath) => {
return new Promise((reslove,reject) => {
fs.readFile(filePath, FILE_ENCODING, (err, data) => {
err ? reject(err) : reslove(data)
})
})
}
const
file1Path = path.join(__dirname, '1.txt'),
file2Path = path.join(__dirname, '2.txt')
const readFileWithAsync = async () => {
const txtFromFile1 = await readFilePromiseCreate(file1Path)
const txtFromFile2 = await readFilePromiseCreate(file2Path)
const result = (txtFromFile1 + txtFromFile2).replace('\n','')
console.log(result)
}
readFileWithAsync()
輸出結(jié)果:
百度首頁的地址是:https://www.baidu.com
我之所以能夠比較順快的寫出這些代碼.完全是基于我對 async/await
& Promise
的幾個簡單的了解.
-
await
后面必須跟一個Promise
. 且它會等待Promise
執(zhí)行完畢一直到reslove
或者reject
. 否則它下面的代碼不會執(zhí)行. - 之所以寫
async
是因為我知道在語法層面上,await
只能在定義為async
的函數(shù)內(nèi)部使用.
僅此而已.
直到我看到上文章開頭的那道面試題,才知道自己對 async/await
的理解遠(yuǎn)遠(yuǎn)不夠.
于是,就看是了新一輪的研究.
async 關(guān)鍵字起了什么作用?
我們都很清楚,函數(shù)是可以設(shè)置返回值的.
async
函數(shù),當(dāng)然也可以使用 return
來設(shè)置返回值.
const getSomething = () => {
return 'getSomething'
}
const asyncGetSomething = async () => {
return 'asyncGetSomething'
}
console.log(getSomething())
console.log(asyncGetSomething())
查看打印結(jié)果:
getSomething
Promise { 'asyncGetSomething' }
發(fā)現(xiàn),在我們定義為 async
的函數(shù)內(nèi)部,如果使用 return
返回了某個值.
此函數(shù)會把這個值使用 Promise.reslove('asyncGetSomething')
包裝起來,而不是簡單返回.
所以結(jié)論1:使用async修飾的函數(shù)的返回值,會被 Promise.reslove()包裝起來.
假如我們直接拿 async
修飾符函數(shù)的 return
返回值,拿到的將是一個 Promise
.
而不是真正在函數(shù)內(nèi)部 return
出來的值.
所以,對于 async
函數(shù)的返回值,我們必須使用 .then
來獲取.
// 普通函數(shù)直接獲取返回值
const value = getSomething()
// Promise 返回值,使用.then獲取
let value2
asyncGetSomething()
.then((res => {
value2 = res
console.log(value2)
}))
console.log(value)
結(jié)果:
getSomething
asyncGetSomething
結(jié)論2:async函數(shù)返回的數(shù)據(jù),同步方式是無法拿到了,必須借助.then來獲取.
補(bǔ)充一下:
關(guān)于 async 函數(shù)沒有返回值
當(dāng)沒有返回值時,返回的也不是簡單的
undefined
仍然也是使用Promise.reslove(undefined)
console.log((async () => { })())
Promise { undefined }
關(guān)于兩種函數(shù)的類型問題.
console.log(async () => { })
console.log(() => { })
[AsyncFunction]
[Function]
擴(kuò)展,比如,我一個方法需要傳遞一個 async
的函數(shù)才行.
async function test (fn) {
if (!Object.prototype.toString.call(fn).includes('AsyncFunction')) {
console.log('需要傳遞一個 async 的函數(shù)!!!')
return
}
let data = await fn()
console.log(data)
}
test(() => { })
test(async () => {
return 'await取出來'
})
結(jié)果
需要傳遞一個 async 的函數(shù)!!!
await取出來
await 到底在等什么?
await 應(yīng)該是單詞 async wait
詞組的縮寫.
我之前一直認(rèn)為, await
后面只能接 Promise
..
但看了一些博客文章,告訴我,其實 await
是一個計算符.
它等待后面表達(dá)式的計算結(jié)果.
await 后面接 Promise 我已經(jīng)知道了.
它會等待 Promise 執(zhí)行完畢(reslove | reject) .
才會執(zhí)行下面的代碼.
那如果我在 await 后面寫的一個除了Promise之外的任意表達(dá)式呢?
async function test () {
let data = await 1 + 1
console.log(data)
let data2 = await 'hello'
console.log(data2)
let data3 = await (() => 'world')()
console.log(data3)
}
test()
結(jié)果:
2
hello
world
所以, await
后面是可以接除了 Promise
之外的其他任意數(shù)據(jù)的.
但是區(qū)別在哪呢?
await 后面接 Promise 和 其他數(shù)據(jù)的區(qū)別.
Promise
和其他的數(shù)據(jù)類型到底有沒有區(qū)別?
一般后面接Promise
,即使在內(nèi)部立即 reslove
出來,它仍然是一個異步的操作.
那如果,await
后面接的是同步數(shù)據(jù)呢?
那我們來利用 await
會阻塞這種做法來證明一下.
async function test () {
let data = await '同步數(shù)據(jù)'
console.log(data)
}
console.log('start')
test()
console.log('end')
那么這里就可能有兩種情況了.
- 如果即使
await
后面跟的表達(dá)式是同步代碼
,仍然會進(jìn)行異步操作的話.返回的結(jié)果應(yīng)該是start end 同步數(shù)據(jù)
- 如果
await
后面跟的表達(dá)式是同步代碼
,它就會按照同步代碼執(zhí)行的邏輯執(zhí)行.輸出的結(jié)構(gòu)應(yīng)該是start 同步數(shù)據(jù) end
運(yùn)行結(jié)果:
start
end
同步數(shù)據(jù)
符合第一種情況.
即使 await
后面跟的不是一個常規(guī)的 Promise
. 而是一個普通的表達(dá)式.它仍然會把計算表達(dá)式的過程丟給 事件循環(huán)
,然后利用回調(diào)的機(jī)制去觸發(fā).
所以結(jié)論:
-
await
只能在被標(biāo)記為async
的函數(shù)內(nèi)部使用. -
await
后面一般接Promise
,然后立即執(zhí)行Promise
.并將Promise
后續(xù)的reslove|reject
丟給 EventLoop ,并阻塞下列代碼執(zhí)行. -
await
后面也可以接普通的表達(dá)式.代碼也不會像同步函數(shù)那樣執(zhí)行.仍然會有一些接了Promise
的痕跡.
那道面試題
-
async
默認(rèn)返回一個Promise
. -
await
后面接Promise
或者 普通表達(dá)式,在執(zhí)行效果上是一致的.
有了上述那些結(jié)論,我們在看看看那道面試題.
async function async1() {
console.log( 'async1 start');
await async2();
console.log( 'async1 end');
}
async function async2() {
console.log( 'async2');
}
console.log( 'script start');
setTimeout(function() {
console.log( 'setTimeout');
}, 0)
async1();
new Promise (function ( resolve ) {
console.log( 'promise1');
resolve();
}).then(function() {
console.log( 'promise2');
})
console.log( 'script end');
- 首先定義了兩個
async
函數(shù)async1
和async2
. 但這里僅僅只是定義,沒有調(diào)用.所以繼續(xù)往下看. - 接著調(diào)用了
console.log('script start')
. ====>script start
- 后面跟了一個
setTimeout
的函數(shù),那這個函數(shù)肯定是丟在EventLoop-1
中了.(畢竟同步代碼還沒走完) - 調(diào)用了
async1()
函數(shù). ====>async1 start
- 在
async1
函數(shù)內(nèi)部第二行調(diào)用await async2()
. -
async2
是一個async
函數(shù),所以返回的肯定是一個Promise
.最差也是Promise.reslove(undefined)
.所以這一步的操作丟在了EventLoop-2
,但其內(nèi)部的console.log('async2')
屬于同步代碼,所以被調(diào)用 ====>async2
-
await
后面的操作就是一個異步的.同時await
會阻塞下面的console.log('async1 end')
- 繼續(xù)往下走,碰到了一個
new Promise(xxxx)
. -
new Promise()
雖然是個Promise
.但執(zhí)行異步操作的這一步是同步的.====>promise1
- 然后第二行代碼,
resolve();
雖然立即reslove()
但由于是異步,仍然丟在了EventLoop-3
中. - 接著就是最后一行代碼
console.log( 'script end');
====>script end
- 到目前為止.所有同步的代碼走完了,
EventLoop
還有三個等待執(zhí)行回調(diào)的setTimeout,async2(),以及 new Promise().then()
- 然后由于
async2
是先于new Promise().then
丟在EventLoop
中去的.且它倆基本就是立馬執(zhí)行的reslove
.所以不會有時間差異.誰先進(jìn)去的誰先被執(zhí)行. ====>async1 end
- 接著
new Promise().then
====>promise2
- 最后執(zhí)行
setTimeout
的回調(diào)函數(shù) ====>setTimeout
所以推理出來的最終輸出結(jié)果是:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
拿出來和一開始的在瀏覽器中運(yùn)行的結(jié)果進(jìn)行對比.
// 瀏覽器環(huán)境執(zhí)行順序
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
發(fā)現(xiàn)結(jié)果是一致的.
總結(jié):
-
async
描述了次方法的返回值一定是一個Promise
.而且是使用Promise.reslove(data)
封裝的. -
await
后面不管是接一個常規(guī)的Promise
還是一個普通的達(dá)表示,其執(zhí)行形式都會和接了Promise
一致.不會因為后面接的是常規(guī)表達(dá)式,而表現(xiàn)出同步代碼的特征. -
await
后面如果接的是一個普通表達(dá)式,則計算表達(dá)式的結(jié)果. -
await
后面如果接的是一個Promise
,則會等待Promise
執(zhí)行reslove(data)
.返回值是data
. - 如果后面的
Promise
有失敗的情況,可以使用.catch
來捕獲,否則會跑出異常.且await
接受到的數(shù)據(jù)是undefined
async function aa () {
let data = await new Promise((resolve, reject) => {
reject('發(fā)生錯了')
}).catch(err => console.log(err)) || '這是默認(rèn)值'
console.log(`data:${data}`)
}
aa()
發(fā)生錯了
data:這是默認(rèn)值