JavaScript 中回調(diào)、Promise 和 Async/Await 的代碼案例
本文將通過代碼示例展示如何使用基于回調(diào)的 API,然后將其改成使用 Promises,最后再用 Async/Await 語法。本文不會(huì)詳細(xì)解釋回調(diào)、promise 和 Async/Await 語法。有關(guān)這些概念的詳細(xì)解釋,請查看 MDN 的 Asynchronous JavaScript[1],它解釋了什么是異步性以及如何用回調(diào)、promise 和 Async/Await 語法處理異步 JavaScript。
如果你對 JavaScript 中的異步有一定的了解,但需要一個(gè)直觀的代碼案例作為參考,那么本文就是給你準(zhǔn)備的。
出于演示目的,我們將使用 fs.readFile[2],這是一個(gè)基于回調(diào)的用于讀取文件的 API。我們將會(huì)先創(chuàng)建一個(gè)包含一些文本的文件 test.txt,然后用 script.js 來打開文件、讀取內(nèi)容并將其輸出到終端。
代碼將首先用回調(diào)實(shí)現(xiàn),然后將其修改為使用 Promise,最后改為使用 Async/Await,而不是直接使用 Promise。
廢話少說,開始!
使用回調(diào)
首先創(chuàng)建一個(gè)目錄,里面包含我們的代碼文件和要進(jìn)行讀取操作的文件。
先創(chuàng)建著兩個(gè)文件;
- $ 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 命令執(zhí)行腳本,會(huì)在終端上輸出“Beam me up, Scotty”:
- $ node script.js
- Beam me up, Scotty [callback]
對于回調(diào)的寫法,異步操作的結(jié)果會(huì)被傳給執(zhí)行異步操作的函數(shù),并由其進(jìn)行處理。
使用 Promise
修改 script.js 并添加一個(gè)使用 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 命令來執(zhí)行腳本:
- $ node script.js
- Beam me up, Scotty [callback]
- Beam me up, Scotty [promise]
使用promise,異步操作的結(jié)果由傳遞給 promise 對象公開的 then 函數(shù)進(jìn)行處理。
使用 Async/Await
修改 script.js 并添加使用 Async/Await 語法的第三個(gè)版本。由于 Async/Await 是一種能讓 promise 更容易的語法,所以 Async/Await 實(shí)現(xiàn)將使用 readFilePromise() 函數(shù)。代碼是這樣的:
- 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: 通過運(yùn)行節(jié)點(diǎn)腳本執(zhí)行腳本.js將打印與此類似的東西,到終端:
- Beam me up, Scotty [callback]
- Beam me up, Scotty [promise]
- Beam me up, Scotty [async-await]
使用 async/await,異步操作的結(jié)果被當(dāng)作同步操作來處理。await 對此負(fù)責(zé),而使用它的函數(shù)必須以 async 關(guān)鍵字開頭。
3種實(shí)現(xiàn)的完整代碼如下:
- 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()
錯(cuò)誤處理
為了驗(yàn)證在 3 種代碼實(shí)現(xiàn)在工作時(shí)錯(cuò)誤處理是否會(huì)按預(yù)期工作,重命名 test.txt 文件并重新運(yùn)行腳本:
- $ 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種實(shí)現(xiàn)都會(huì)顯示錯(cuò)誤處理代碼(僅將錯(cuò)誤輸出到控制臺(tái)),說明它們都按預(yù)期執(zhí)行了。