說說 JavaScript 中的 async 和 await

ES7 引入了 async/await,這是 JavaScript 異步編程的一個比較大的改進。我們可以像寫同步代碼一些編寫異步代碼,避免了回調地獄,同時也代碼也比 Promise 更易於閱讀。

async 和 await 也是面試經常被問到的東西,之前一直只限於會用,並不太理解內部的實現原理。今天就來好好探究探究,JavaScript 中的 async 和 await 到底是怎麼工作的。

async

async 就是異步的意思,在函數的定義前加上 async 關鍵字,表示這是一個異步函數,意味着該函數的執行不會阻塞後面代碼的執行。

先來看一個簡單的例子:

async function hello() {
  console.log('hello');
}

hello();
console.log('world');

可以猜猜這段代碼的輸出順序是什麼?

輸出的順序如下:

hello
world

你可能要跳出來罵我了,前面不是說不會阻塞後面代碼的執行麼,爲什麼還是按順序輸出的?

先別急着罵,容我再進一步解釋。

我們先一起來看看 hello 函數的返回值是什麼。

async function hello() {
  console.log('hello');
  return 'hello';
}

console.log(hello());

console.log('world');

上面的代碼輸出如下:

hello
Promise {<resolved>: "hello"}
world

hello()函數並沒有指定返回值,但是默認返回了一個 Promise 對象,而 Promise 的。

emm…看到這個 Promise,似乎有些清楚了。

也就是說,async 函數返回一個 Promise 對象,因此可以使用 then 方法添加回調函數。並且 async 函數內部 return 語句返回的值,會成爲 then 方法回調函數的參數。

再修改一下上面的代碼:

async function hello() {
  console.log('hello before');
  return 'hello after';
}

hello().then(value => console.log(value))

console.log('world');

上面的代碼輸出結果:

hello before
world
hello after

爲什麼 hello after 會在 world 後輸出呢?這是因爲 Promise 在 JavaScript 內部的運行機制,Promise 屬於「微任務」,在當前腳本代碼執行完後,立刻執行的,並沒有參與事件循環。

既然返回值是 Promise,那麼說明函數的執行也有對應的狀態,如果函數正常執行,內部調用 Promise.resolve(),如果函數有錯誤,則調用 Promise.reject() 返回一個 Promise 對象。

繼續修改上面的代碼:

async function hello(flag) {
  if (flag) {
    return 'hello';
  } else {
    throw 'failed';
  }
}

console.log(hello(true));
console.log(hello(false));

代碼輸出:

Promise { resolved }
Promise { rejected }

await

await 就是等待的意思,即等待請求或者資源。await 後面可以接任何普通表達式,但一般會在 await 後面放一個返回 promise 對象的表達式。

注意 :await 關鍵字只能放到 async 函數裏面。

如果 await 等待的是一個 Promise,它就會阻塞後面的代碼,等着 Promise 對象 resolve,然後得到 resolve 的值,作爲 await 表達式的運算結果。

看到「阻塞」兩個字,心裏咯噔一下。尼瑪,這一阻塞,函數外的代碼不會也暫停執行麼。

**這個你不用擔心,這就是 await 必須用在 async 函數中的原因。**async 函數調用不會造成阻塞,就像上文所描述的。它內部所有的阻塞都被封裝在一個 Promise 對象中異步執行。

先來看下面的代碼:

function delay(seconds) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(seconds)
    }, seconds);
  });
}

async function hello() {
  console.log('hello');
  let result = await delay(2000);
  console.log(result);
}
hello();

console.log('world');

上面定義了一個 delay 函數,接受一個 seconds 參數。函數返回一個 Promise 對象,這個 Promise 對象傳入的是一個定時器。

然後在 hello函數中去調用 delay 函數,指定 await 關鍵字。當代碼執行到result 這一行是,會阻塞 2 秒鐘。等 Promise 的 resolve,返回執行結果,然後賦值給 result,代碼纔開始繼續執行,執行下一行的 console.log 語句。

輸出結果如下:

hello
world
// 兩秒後...
2000

這樣我們就以同步代碼的方式編寫了一段異步請求的函數,如果遇到多個異步的代碼,await 更能體現出寫法上的可讀性。

例如下面的代碼:

async function hello() {
  console.log('hello');
  let result1 = await delay(2000);
  let result2 = await delay(3000);
  let result3 = await delay(4000);
  console.log(result1+result2+result3);
}

如果用 Promise :

delay(2000).then(res=> {
  console.log(res);
  return delay(3000);
}).then(res => {
  console.log(res);
  return delay(4000);
}).then(res => {
  console.log(res);
})

可以看到,使用 await 寫異步代碼真的非常的方便,而且代碼的可讀性非常的好,也沒有回調地獄了,小夥伴們趕緊用起來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章