promise async await 執行順序和面試題詳解


異步編程的最高境界就是不關心它是否是異步。async、await很好的解決了這一點,將異步強行轉換為同步處理。
async/await與promise不存在誰代替誰的說法,因為async/await是寄生於Promise,是Generater的語法糖。

 

溫馨提示:如果你已經知道了關於async await的基本用法,請直接看分割線以下內容

Generator

1. 調用 Generator 函數,返回一個遍歷器對象,代表 Generator 函數的內部指針。
2.  以后,每次調用遍歷器對象的next方法,就會返回一個有着value和done兩個屬性的對象。
3.  value屬性表示當前的內部狀態的值,是yield表達式后面那個表達式的值;done屬性是一個布爾值,表示是否遍歷結束。
 
function* helloWorld () {
    yield 'hello'
    yield 'world'
    return 'ending'
}

var hw = helloWorld()
console.log(hw)         // helloWorld {<suspended>}
console.log(hw.next())  // {value: "hello", done: false}
console.log(hw.next())  // {value: "world", done: false}
console.log(hw.next())  // {value: "ending", done: false}
console.log(hw.next())  // {value: undefined, done: true}

  

async
 
1. async其實就是對Generator的封裝,只不過async可以自動執行next()。
2. async必須等到里面所有的await執行完,async才開始return,返回的Promise狀態才改變。除非遇到return和錯誤。
3. async默認返回一個Promise,如果return不是一個Promise對象,就會被轉為立即resolve的Promise,可以在then函數中獲取返回值。
 
async function fn() {
    await console.log(1111111)
    await console.log(2222222)
    await console.log(3333333)
}
fn() 
// 1111111
// 2222222
// 3333333

 

async function fn () {
    await 100
    await 200
    return 300
}
fn().then(res => {
    console.log(res) // 300
}) 
 
 再看下面這個例子:

 打印結果如下:(返回的是promise對象)

如果在async函數中拋出了錯誤,則終止錯誤結果,不會繼續向下執行:

 

如果希望一個await失敗,后面的繼續執行,可以使用try...catch或者在await后面的Promise跟一個catch方法:
async function f() {
  try {
    await Promise.reject('出錯了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))   // hello world

// catch
async function f() {
  await Promise.reject('出錯了')
    .catch(e => console.log(e));   // 出錯了
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))  // hello world

 

================================   分割線   ==================================

面試題

【例1】

async function async1() {
    console.log("async1 start");
    await async2();
    console.log("async1 end");
    return 'async return';
}

async function async2() {
    console.log("async2");
}

console.log("script start");

setTimeout(function() {
    console.log("setTimeout");
}, 0);

async1().then(function (message) { console.log(message) });

new Promise(function(resolve) {
    console.log("promise1");
    resolve();
}).then(function() {
    console.log("promise2");
});

console.log("script end");

輸出順序如下:

 

這道題目考查的是我們對 事件循環  任務隊列   的理解:

事件循環(Event Loop): 

 1. JS會首先判斷代碼是同步還是異步,同步進入主線程,異步進入任務隊列;
 2. 同步任務進入主線程后一直執行,直到主線程空閑后,才會去任務隊列中查看是否有可執行的異步任務,如果有就推入主線程中執行;
 3. 事件循環是一個先進先出(FIFO)隊列,這說明回調是按照它們被加入隊列的順序執行的。

 

 [ 分析 ]:

1.  在單線程的js中,異步代碼會被放入一個事件隊列,等到所有其他代碼執行后再執行,而不會阻塞線程。我們從上到下看,首先打印:script  start;

2.  setTimeout / setInterval 要放到任務隊列的末尾,等待后續執行。繼續往下走;

      此時的任務隊列:  setTimeout

 

3.  async1 開始執行,當函數里遇到await時,暫停執行(await所在行放在本次執行完),而 async1 函數 未完成部分被添加到任務隊列;

     此時的任務隊列:  async1    setTimeout

 

4.  new Promise() 實例對象被new出來后,它里面的promise1會立刻打印,然后又遇到 then, 此時 promise 實例 被添加到任務隊列;

     此時的任務隊列:  async1    promise實例    setTimeout

 

5.  接着打印:script end。至此,同步代碼已執行完畢。

     而我們的任務隊列中還存在着 async1, promise對象, setTimeout異步回調;

6.  由於異步代碼第一次執行時,async1 函數 要早於 promise對象,所以緊接着 async1 函數繼續執行沒有執行完成的部分,執行完畢后,退出任務隊列,打印:async1 end。然后把它的 then 邏輯添加到任務       隊列中;

       此時的任務隊列: promise實例   async1的then邏輯部分  setTimeout

 

7.  promise 實例 繼續執行它的 then 的邏輯,打印:promise2。執行完畢后,退出任務隊列;

       此時的任務隊列: async1的then邏輯部分  setTimeout

 

8.  async 函數執行 then 邏輯;

       此時的任務隊列: setTimeout

 

9.  setTimeout是宏任務會在最后執行。

 

     【 補充說明 】:

      1.  因為在 async1 函數內部被一個 await 分為兩部分,需要分兩步才可執行完。3的時候執行完第一步后暫停,而將剩余部分放到任務隊列等待執行;

      2.  在5的時候同步代碼已執行完畢,所以 js 回過頭來去任務隊列上找未完成的異步任務,這個時候首先去執行 async1(在6時候), 因為它最先被放到任務隊列;

      3.  在6時候,async1 函數並沒有緊接着執行 then 的邏輯,而是繼續執行沒有執行完成的部分,而這次當 async1 執行完畢之后,會把 then 放到任務隊列當中,且排在promise對象之后。7的時候promise             實例繼續執行下一步異步代碼,執行完畢之后,任務隊列此時只剩下 async1 的 then 邏輯,這時執行棧會執行 async1 的 then 邏輯。

 

 【例2】:

var p = new Promise((res,rej) => {
    res('hello one')
    console.log('good morning')
})

function hello() {
    console.log('hello begins')
    return p
}

hello().then(res => {
    console.log(res)
    console.log('hello1111111111')
    return 'hello two'
}).then(res => {
    console.log(res)
    console.log('hello22222222222')
    return 'hello three'
}).then(res => {
    console.log(res)
    console.log('hello33333333333')
})

function test1 () {
    console.log('test1')
}

async function asy () {
    console.log('asy begins')
    await console.log('asy---111111')

    console.log('async1')
    await console.log('asy---222222')

    console.log('asy ends')
}

asy()

test1()

function* gnrt () {
    console.log(1)
    yield console.log(11111111)
    
    console.log(2)
    yield console.log(22222222)
    
    console.log(3)
    yield console.log(33333333)
}

var result = gnrt()
result.next()
result.next()
result.next()

輸出順序如下:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM