async & await 學(xué)習(xí)筆記

async & await 再次學(xué)習(xí).

我從簡述某博主的一片博客中看到了一到關(guān)于 async/await 的面試題.

博客地址:async/await 和 promise

代碼如下:

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/awaitES7 推出來的語法.
一般搭配 Promise 來使用.
可以讓我們從同步代碼的方式,來書寫異步代碼.

比如下面這個例子.

場景:我需要按順序的將1.txt和2.txt的內(nèi)容讀取出來,然后拼接成一個字符串.用async/await & promise來做.

1.txt

百度首頁的地址是:

2.txt

https://www.baidu.com

我希望打印結(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');

  1. 首先定義了兩個 async 函數(shù) async1async2. 但這里僅僅只是定義,沒有調(diào)用.所以繼續(xù)往下看.
  2. 接著調(diào)用了 console.log('script start') . ====> script start
  3. 后面跟了一個 setTimeout 的函數(shù),那這個函數(shù)肯定是丟在EventLoop-1中了.(畢竟同步代碼還沒走完)
  4. 調(diào)用了 async1() 函數(shù). ====> async1 start
  5. async1 函數(shù)內(nèi)部第二行調(diào)用 await async2().
  6. async2 是一個 async 函數(shù),所以返回的肯定是一個Promise.最差也是 Promise.reslove(undefined).所以這一步的操作丟在了 EventLoop-2 ,但其內(nèi)部的 console.log('async2')屬于同步代碼,所以被調(diào)用 ====> async2
  7. await 后面的操作就是一個異步的.同時 await會阻塞下面的 console.log('async1 end')
  8. 繼續(xù)往下走,碰到了一個 new Promise(xxxx).
  9. new Promise() 雖然是個 Promise.但執(zhí)行異步操作的這一步是同步的.====> promise1
  10. 然后第二行代碼,resolve();雖然立即reslove() 但由于是異步,仍然丟在了 EventLoop-3 中.
  11. 接著就是最后一行代碼 console.log( 'script end'); ====> script end
  12. 到目前為止.所有同步的代碼走完了,EventLoop 還有三個等待執(zhí)行回調(diào)的 setTimeout,async2(),以及 new Promise().then()
  13. 然后由于 async2 是先于 new Promise().then 丟在 EventLoop 中去的.且它倆基本就是立馬執(zhí)行的 reslove.所以不會有時間差異.誰先進(jìn)去的誰先被執(zhí)行. ====> async1 end
  14. 接著 new Promise().then ====> promise2
  15. 最后執(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é):

  1. async 描述了次方法的返回值一定是一個 Promise.而且是使用 Promise.reslove(data)封裝的.
  2. await 后面不管是接一個常規(guī)的 Promise 還是一個普通的達(dá)表示,其執(zhí)行形式都會和接了 Promise一致.不會因為后面接的是常規(guī)表達(dá)式,而表現(xiàn)出同步代碼的特征.
  3. await 后面如果接的是一個普通表達(dá)式,則計算表達(dá)式的結(jié)果.
  4. await 后面如果接的是一個Promise,則會等待Promise 執(zhí)行 reslove(data).返回值是data.
  5. 如果后面的 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)值
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谚咬,隨后出現(xiàn)的幾起案子褐墅,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件礁哄,死亡現(xiàn)場離奇詭異映琳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)纽谒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門证膨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鼓黔,你說我怎么就攤上這事央勒〔患” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵订歪,是天一觀的道長脖祈。 經(jīng)常有香客問我,道長刷晋,這世上最難降的妖魔是什么盖高? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮眼虱,結(jié)果婚禮上喻奥,老公的妹妹穿的比我還像新娘。我一直安慰自己捏悬,他們只是感情好撞蚕,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布濒憋。 她就那樣靜靜地躺著议经,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悴了。 梳的紋絲不亂的頭發(fā)上寇钉,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天刀疙,我揣著相機(jī)與錄音,去河邊找鬼扫倡。 笑死谦秧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撵溃。 我是一名探鬼主播疚鲤,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缘挑!你這毒婦竟也來了集歇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤语淘,失蹤者是張志新(化名)和其女友劉穎诲宇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亏娜,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡焕窝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了维贺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片它掂。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出虐秋,到底是詐尸還是另有隱情榕茧,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布客给,位于F島的核電站用押,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏靶剑。R本人自食惡果不足惜蜻拨,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桩引。 院中可真熱鬧缎讼,春花似錦、人聲如沸坑匠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厘灼。三九已至夹纫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間设凹,已是汗流浹背舰讹。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留围来,地道東北人跺涤。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓匈睁,卻偏偏與公主長得像监透,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子航唆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容