JavaScript 中回調、Promise 和 Async/Await 的代碼案例
本文將通過代碼示例展示如何使用基于回調的 API,然后將其改成使用 Promises,最后再用 Async/Await 語法。本文不會詳細解釋回調、promise 和 Async/Await 語法。有關這些概念的詳細解釋,請查看 MDN 的 Asynchronous JavaScript[1],它解釋了什么是異步性以及如何用回調、promise 和 Async/Await 語法處理異步 JavaScript。
如果你對 JavaScript 中的異步有一定的了解,但需要一個直觀的代碼案例作為參考,那么本文就是給你準備的。
出于演示目的,我們將使用 fs.readFile[2],這是一個基于回調的用于讀取文件的 API。我們將會先創建一個包含一些文本的文件 test.txt,然后用 script.js 來打開文件、讀取內容并將其輸出到終端。
代碼將首先用回調實現,然后將其修改為使用 Promise,最后改為使用 Async/Await,而不是直接使用 Promise。
廢話少說,開始!
使用回調
首先創建一個目錄,里面包含我們的代碼文件和要進行讀取操作的文件。
先創建著兩個文件;
- $ mkdir ~/code
- $ touch ~/code/script.js
- $ echo "Beam me up, Scotty" > ~/code/test.txt
- $ cd ~/code/
在 script.js 文件中,輸入以下代碼:
- const fs = require("fs")
- function readFileCallBack() {
- fs.readFile("./test.txt", 'utf8', (err, data) => {
- if (err) {
- console.error(err)
- return
- }
- console.log(data.trim() + " [callback]")
- })
- }
- readFileCallBack()
通過 node script.js 命令執行腳本,會在終端上輸出“Beam me up, Scotty”:
- $ node script.js
- Beam me up, Scotty [callback]
對于回調的寫法,異步操作的結果會被傳給執行異步操作的函數,并由其進行處理。
使用 Promise
修改 script.js 并添加一個使用 promise 的 readFileCallback 版本。代碼如下:
- function readFilePromise() {
- return new Promise((resolve, reject) => {
- fs.readFile("./test.txt", 'utf8', (err, data) => {
- if (err) {
- reject(err)
- return
- }
- resolve(data.trim())
- })
- });
- }
- readFilePromise()
- .then(data => console.log(data + " [promise]"))
- .catch(err => console.log(err))
通過 node script.js 命令來執行腳本:
- $ node script.js
- Beam me up, Scotty [callback]
- Beam me up, Scotty [promise]
使用promise,異步操作的結果由傳遞給 promise 對象公開的 then 函數進行處理。
使用 Async/Await
修改 script.js 并添加使用 Async/Await 語法的第三個版本。由于 Async/Await 是一種能讓 promise 更容易的語法,所以 Async/Await 實現將使用 readFilePromise() 函數。代碼是這樣的:
- async function readFileAsync() {
- try {
- const data = await readFilePromise()
- console.log(data.trim() + " [async-await]")
- } catch (err) {
- console.log(err)
- }
- }
- readFileAsync()
Executing the script by running node script.js will print something similar to this, to the terminal: 通過運行節點腳本執行腳本.js將打印與此類似的東西,到終端:
- Beam me up, Scotty [callback]
- Beam me up, Scotty [promise]
- Beam me up, Scotty [async-await]
使用 async/await,異步操作的結果被當作同步操作來處理。await 對此負責,而使用它的函數必須以 async 關鍵字開頭。
3種實現的完整代碼如下:
- const fs = require("fs")
- // callback
- function readFileCallBack() {
- fs.readFile("./test.txt", 'utf8', (err, data) => {
- if (err) {
- console.error(err)
- return
- }
- console.log(data.trim() + " [callback]")
- })
- }
- readFileCallBack()
- // promise
- function readFilePromise() {
- return new Promise((resolve, reject) => {
- fs.readFile("./test.txt", 'utf8', (err, data) => {
- if (err) {
- reject(err)
- return
- }
- resolve(data.trim())
- })
- });
- }
- readFilePromise()
- .then(data => console.log(data + " [promise]"))
- .catch(err => console.log(err))
- // async/await
- async function readFileAsync() {
- try {
- const data = await readFilePromise()
- console.log(data.trim() + " [async-await]")
- } catch (err) {
- console.log(err)
- }
- }
- readFileAsync()
錯誤處理
為了驗證在 3 種代碼實現在工作時錯誤處理是否會按預期工作,重命名 test.txt 文件并重新運行腳本:
- $ mv test.txt test.txt.backup
- $ node script.js
- [Error: ENOENT: no such file or directory, open './test.txt'] {
- errno: -2,
- code: 'ENOENT',
- syscall: 'open',
- path: './test.txt'
- }
- [Error: ENOENT: no such file or directory, open './test.txt'] {
- errno: -2,
- code: 'ENOENT',
- syscall: 'open',
- path: './test.txt'
- }
- [Error: ENOENT: no such file or directory, open './test.txt'] {
- errno: -2,
- code: 'ENOENT',
- syscall: 'open',
- path: './test.txt'
- }
3種實現都會顯示錯誤處理代碼(僅將錯誤輸出到控制臺),說明它們都按預期執行了。