[每日一題]面試官問:Async/Await 如何通過同步的方式實現非同步?

saucxs發表於2021-01-05

關注「鬆寶寫程式碼」,精選好文,每日一題

​時間永遠是自己的

每分每秒也都是為自己的將來鋪墊和增值

作者:saucxs | songEagle

一、前言

2020.12.23 日剛立的 flag,每日一題,題目型別不限制,可以是:演算法題,面試題,闡述題等等。

本文是「每日一題」第 6 題:面試官問:Async/Await 如何通過同步的方式實現非同步?

每日一題

往期「每日一題」:

二、Async/Await 如何通過同步的方式實現非同步?

這個題目本身不是特別難,只能說是作為社招的基礎面試題,但是如果想回答好這道題也不是很容易。

不信接著往下看:

1、概括的說

一個函式如果加上 async ,那麼該函式就會返回一個 Promise。

await 只能在 async 函式中使用,可以把 async 看成將函式返回值使用 Promise.resolve() 包裹了下。

async 和 await 相比直接使用 Promise 來說,優勢在於處理 then 的呼叫鏈,能夠更清晰準確的寫出程式碼。缺點在於濫用 await 可能會導致效能問題,因為 await 會阻塞程式碼,也許之後的非同步程式碼並不依賴於前者,但仍然需要等待前者完成,導致程式碼失去了併發性。

我們來看一下程式碼例項:

async function test() {
  return "1";
}
console.log(test()); // -> Promise {<resolved>: "1"}

我們再來看一下這個例項:

function sleep() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('finish')
      resolve("sleep");
    }, 2000);
  });
}
async function test() {
  let value = await sleep();
  console.log("object");
}
test()

上面程式碼會先列印 finish 然後再列印 object 。因為 await 會等待 sleep 函式 resolve ,所以即使後面是同步程式碼,也不會先去執行同步程式碼再來執行非同步程式碼。

2、亮點回答

首先,js 是單執行緒的(重複三遍),所謂單執行緒,

意思就是說:執行程式碼是一行一行的往下走(即所謂的同步),

如果上面的沒執行完,那就只能等著。

還是舉個例子:

function test() {
  let d = Date.now();
  for (let i = 0; i < 1e8; i++) {}
  console.log(Date.now() - d); // 62ms左右
}
function test1() {
  let d = Date.now();

  console.log(Date.now() - d); // 0
}
test();
test1();

上面僅僅是一個 for 迴圈,而在實際應用中,會有大量的網路請求,它的響應時間是不確定的,這種情況下也要等待嗎?

顯然是不行的,因而 js 設計了非同步,即 發起網路請求(諸如 IO 操作,定時器),由於需要等伺服器響應,就先不理會,而是去做其他的事兒,等請求返回了結果的時候再說(即非同步)。

那麼如何實現非同步呢?其實我們平時已經在大量使用了,那就是 callback,實現非同步的核心就是回撥鉤子,將 cb 作為引數傳遞給非同步執行函式,當有了結果後在觸發 cb。想了解更多,可以去看看 event-loop 機制。

之前這種函式巢狀,大量的回撥函式,使程式碼閱讀起來晦澀難懂,不直觀,形象的稱之為回撥地獄(callback hell),所以為了在寫法上能更通俗一點,es6+陸續出現了 Promise、Generator、Async/await,力求在寫法上簡潔明瞭,可讀性強。

async/await 是參照 Generator 封裝的一套非同步處理方案,可以理解為 Generator 的語法糖,

所以瞭解 async/await 就不得不講一講 Generator,以後我們可以講一下這個。

而 Generator 又依賴於迭代器Iterator,以後我們可以講一下這個。

終於找到源頭了:單向連結串列,以後可以講一下這個。

可以看到,async function 代替了 function*,await 代替了 yield,同時也無需自己手寫一個自動執行器 run 了

現在再來看看async/await 的特點:

  • 當 await 後面跟的是 Promise 物件時,才會非同步執行,其它型別的資料會同步執行
  • 返回的仍然是個 Promise 物件,上面程式碼中的 return 'done'; 會直接被下面 then 函式接收到

3、進階回答

async/await 是參照 Generator 封裝的一套非同步處理方案,可以理解為 Generator 的語法糖,

所以瞭解 async/await 就不得不講一講 Generator,

而 Generator 又依賴於迭代器Iterator,

所以就得先講一講 Iterator,

而 Iterator 的思想呢又來源於單向連結串列,

終於找到源頭了:單向連結串列

3.1 什麼是單向連結串列?

我們看一下wiki的說明:連結串列(Linked list)是一種常見的基礎資料結構,是一種線性表,但是並不會按線性的順序儲存資料,而是在每一個節點裡存到下一個節點的指標(Pointer)。由於不必須按順序儲存,連結串列在插入的時候可以達到 o(1)的複雜度,比另一種線性表順序錶快得多,但是查詢一個節點或者訪問特定編號的節點則需要 o(n)的時間,而順序表響應的時間複雜度分別是 o(logn)和 o(1)。

總結一下連結串列優點:

  • 無需預先分配記憶體
  • 插入/刪除節點不影響其他節點,效率高(典型的例子:git commit)

單向連結串列:是連結串列中最簡單的一種,它包含兩個域,一個資訊域和一個指標域。這個連結指向列表中的下一個節點,而最後一個節點則指向一個空值。

lianbiao

一個單向連結串列包含兩個值: 當前節點的值和一個指向下一個節點的連結

單鏈特點:節點的連結方向是單向的;相對於陣列來說,單連結串列的的隨機訪問速度較慢,但是單連結串列刪除/新增資料的效率很高。

理解 js 原型鏈/作用域鏈的話,理解這個很容易,他們是相通的。

3.2 Iterator

Iterator 翻譯過來就是迭代器(遍歷器)讓我們先來看看它的遍歷過程(類似於單向連結串列):

  • 建立一個指標物件,指向當前資料結構的起始位置:
  • 第一次呼叫指標物件的 next 方法,將指標指向資料結構的第一個成員
  • 第二次呼叫指標物件的 next 方法,將指標指向資料結構的第二個成員
  • 不斷的呼叫指標物件的 next 方法,直到它指向資料結構的結束位置

一個物件要變成可迭代的,必須實現 @@iterator 方法,即物件(或它原型鏈上的某個物件)必須有一個名字是 Symbol.iterator 的屬性(原生具有該屬性的有:字串、陣列、類陣列的物件、Set 和 Map):

當一個物件需要被迭代的時候(比如開始用於一個 for..of 迴圈中),它的 @@iterator 方法被呼叫並且無引數,然後返回一個用於在迭代中獲得值的迭代器

3.3 Generator

Generator:生成器物件是生成器函式(GeneratorFunction)返回的,它符合可迭代協議和迭代器協議,既是迭代器也是可迭代物件,可以呼叫 next 方法,但它不是函式,更不是建構函式.

呼叫一個生成器函式並不會馬上執行它裡面的語句,而是返回一個這個生成器的迭代器物件,當這個迭代器的 next() 方法被首次(後續)呼叫時,其內的語句會執行到第一個(後續)出現 yield 的位置為止(讓執行處於暫停狀),yield 後緊跟迭代器要返回的值。或者如果用的是 yield*(多了個星號),則表示將執行權移交給另一個生成器函式(當前生成器暫停執行),呼叫 next() (再啟動)方法時,如果傳入了引數,那麼這個引數會作為上一條執行的 yield 語句的返回值,

我們來總結一下 Generator 的本質,暫停,它會讓程式執行到指定位置先暫停(yield),然後再啟動(next),再暫停(yield),再啟動(next),而這個暫停就很容易讓它和非同步操作產生聯絡,因為我們在處理非同步時:開始非同步處理(網路求情、IO 操作),然後暫停一下,等處理完了,再該幹嘛幹嘛。不過值得注意的是,js 是單執行緒的(又重複了三遍),非同步還是非同步,callback 還是 callback,不會因為 Generator 而有任何改變。

3.4 Async/Await

async/await 是 Generator 的語法糖,就是一個自執行的generate函式。利用generate函式的特性把非同步的程式碼寫成“同步”的形式。

覺得這樣是不是可以清晰點了。

Reference

各種福利

1、位元組內推福利

回覆「校招」獲取內推碼

回覆「社招」獲取內推

回覆「實習生」獲取內推

後續會有更多福利

2、學習資料福利

回覆「演算法」獲取演算法學習資料

3、每日一題

謝謝支援

鬆寶寫程式碼

1、喜歡的話可以「分享,點贊,在看」三連哦。

2、作者暱稱:saucxs,songEagle,鬆寶寫程式碼。「鬆寶寫程式碼」公眾號作者,每日一題,實驗室等。一個愛好折騰,致力於全棧,正在努力成長的位元組跳動工程師,星辰大海,未來可期。內推位元組跳動各個部門各個崗位。

3、長按下面圖片,關注「鬆寶寫程式碼」,是獲取開發知識體系構建,精選文章,專案實戰,實驗室,每日一道面試題,進階學習,思考職業發展,涉及到JavaScript,Node,Vue,React,瀏覽器,http等領域,希望可以幫助到你,我們一起成長~

相關文章