JavaScript異步編程的關鍵點
本文將深入淺出地探討JavaScript異步編程,涵蓋回調函數、Promise和async/await三種主要方法。我們將通過示例代碼、要點總結和深入學習資源鏈接,幫助您掌握JavaScript異步編程的核心概念。
內容概要:
JavaScript的異步特性
JavaScript通常被稱為“異步”語言。這意味著什麼?它如何影響開發?近年來,其方法又發生了哪些變化?
考慮以下代碼:
result1 = doSomething1(); result2 = doSomething2(result1);
大多數語言同步處理每一行代碼。第一行運行並返回結果,第二行只有在第一行完成後才會運行,無論第一行需要多長時間。
單線程處理
JavaScript運行在一個單線程上。在瀏覽器標籤頁中執行時,其他所有操作都會停止。這是必要的,因為對頁面DOM的更改不能在並行線程上發生;如果一個線程重定向到不同的URL,而另一個線程試圖附加子節點,這將是危險的。
用戶很少注意到這一點,因為處理以小的塊快速進行。例如,JavaScript檢測到按鈕點擊,運行計算,並更新DOM。完成後,瀏覽器就可以處理隊列中的下一個項目。
(備註:其他語言,如PHP,也使用單線程,但可能由多線程服務器(如Apache)管理。對同一PHP頁面的兩個同時請求可以啟動兩個線程,運行PHP運行時的隔離實例。)
使用回調函數實現異步操作
單線程帶來了一個問題。當JavaScript調用“緩慢”的過程(例如瀏覽器的Ajax請求或服務器上的數據庫操作)時會發生什麼?該操作可能需要幾秒鐘甚至幾分鐘。瀏覽器在等待響應時將被鎖定。在服務器上,Node.js應用程序將無法處理進一步的用戶請求。
解決方案是異步處理。它不是等待完成,而是告訴一個進程在結果準備好時調用另一個函數。這被稱為回調函數,它作為參數傳遞給任何異步函數。
例如:
doSomethingAsync(callback1); console.log('finished'); // 当doSomethingAsync完成时调用 function callback1(error) { if (!error) console.log('doSomethingAsync complete'); }
doSomethingAsync函數接受一個回調函數作為參數(只傳遞該函數的引用,因此開銷很小)。 doSomethingAsync需要多長時間並不重要;我們只知道callback1將在未來的某個時間點執行。控制台將顯示:
result1 = doSomething1(); result2 = doSomething2(result1);
您可以閱讀更多關於回調函數的信息:深入理解JavaScript回調函數
回調地獄
通常,回調函數只被一個異步函數調用。因此,可以使用簡潔的匿名內聯函數:
doSomethingAsync(callback1); console.log('finished'); // 当doSomethingAsync完成时调用 function callback1(error) { if (!error) console.log('doSomethingAsync complete'); }
可以通過嵌套回調函數來完成一系列異步調用。例如:
<code>finished doSomethingAsync complete</code>
不幸的是,這引入了回調地獄——一個臭名昭著的概念,甚至有自己的網頁!代碼難以閱讀,並且在添加錯誤處理邏輯時會變得更糟。
在客戶端編碼中,回調地獄相對較少見。如果您正在進行Ajax調用、更新DOM並等待動畫完成,它可能會深入兩到三層,但通常仍然易於管理。
對於操作系統或服務器進程,情況就不同了。 Node.js API調用可能會接收文件上傳、更新多個數據庫表、寫入日誌並在發送響應之前進行進一步的API調用。
您可以閱讀更多關於回調地獄的信息:告別回調地獄
Promise
ES2015 (ES6) 引入了Promise。底層仍然使用回調函數,但Promise提供了更清晰的語法來鍊式異步命令,使它們按順序運行(下一節將詳細介紹)。
為了啟用基於Promise的執行,必須更改基於異步回調的函數,以便它們立即返回一個Promise對象。該對象承諾在未來的某個時間點運行兩個函數之一(作為參數傳遞):
在下面的示例中,數據庫API提供了一個接受回調函數的connect方法。外部asyncDBconnect函數立即返回一個新的Promise,並在建立連接或失敗後運行resolve或reject:
doSomethingAsync(error => { if (!error) console.log('doSomethingAsync complete'); });
Node.js 8.0 提供了一個util.promisify()
實用程序,用於將基於回調的函數轉換為基於Promise的替代方案。有兩個條件:
示例:
async1((err, res) => { if (!err) async2(res, (err, res) => { if (!err) async3(res, (err, res) => { console.log('async1, async2, async3 complete.'); }); }); });
異步鍊式調用
任何返回Promise的內容都可以啟動一系列在.then()
方法中定義的異步函數調用。每個函數都接收來自前一個resolve的結果:
const db = require('database'); // 连接到数据库 function asyncDBconnect(param) { return new Promise((resolve, reject) => { db.connect(param, (err, connection) => { if (err) reject(err); else resolve(connection); }); }); }
同步函數也可以在.then()
塊中執行。返回值傳遞給下一個.then()
(如果有)。
.catch()
方法定義一個函數,當任何之前的reject被觸發時調用。此時,不會再運行任何.then()
方法。您可以在整個鏈中使用多個.catch()
方法來捕獲不同的錯誤。
ES2018 引入了一個.finally()
方法,無論結果如何,它都會運行任何最終邏輯——例如,清理、關閉數據庫連接等等。它在所有現代瀏覽器中都受支持:
result1 = doSomething1(); result2 = doSomething2(result1);
Promise的未來?
Promise減少了回調地獄,但也帶來了自身的問題。
教程經常沒有提到整個Promise鍊是異步的。任何使用一系列Promise的函數都應該返回它自己的Promise,或者在最終的.then()
、.catch()
或.finally()
方法中運行回調函數。
Promise的語法通常比回調函數更複雜,有很多容易出錯的地方,調試也可能很麻煩。但是,學習基礎知識至關重要。
您可以閱讀更多關於Promise的信息:JavaScript Promise概述
async/await
Promise可能令人生畏,因此ES2017引入了async和await。雖然它可能只是語法糖,但它使Promise變得更加容易使用,您可以完全避免.then()
鏈。考慮以下基於Promise的示例:
doSomethingAsync(callback1); console.log('finished'); // 当doSomethingAsync完成时调用 function callback1(error) { if (!error) console.log('doSomethingAsync complete'); }
要使用async/await重寫此代碼:
<code>finished doSomethingAsync complete</code>
await有效地使每個調用看起來像是同步的,同時不會佔用JavaScript的單線程處理。此外,async函數總是返回一個Promise,因此它們反過來可以被其他async函數調用。
async/await代碼可能不會更短,但好處很多:
也就是說,並非一切都是完美的……
Promise的升級
async/await依賴於Promise,而Promise最終依賴於回調函數。這意味著您仍然需要了解Promise的工作原理。
此外,當處理多個異步操作時,沒有Promise.all或Promise.race的直接等效項。很容易忘記Promise.all,它比使用一系列不相關的await命令更高效。
try/catch的局限性
如果您省略了任何失敗的await周圍的try/catch,async函數將靜默退出。如果您有一長串異步await命令,您可能需要多個try/catch塊。
一種替代方法是高階函數,它捕獲錯誤,從而使try/catch塊變得不必要(感謝@wesbos的建議)。
但是,如果應用程序必須以與其他錯誤不同的方式對某些錯誤做出反應,則此選項可能不實用。
儘管有一些缺點,但async/await是對JavaScript的優雅補充。
您可以閱讀更多關於使用async/await的信息:JavaScript async/await入門指南,附示例
JavaScript異步編程之旅
在JavaScript中,異步編程是一個無法避免的挑戰。回調函數在大多數應用程序中都是必不可少的,但很容易陷入深度嵌套的函數中。
Promise抽象了回調函數,但有很多語法陷阱。轉換現有函數可能是一項苦差事,而.then()鏈仍然看起來很混亂。
幸運的是,async/await帶來了清晰度。代碼看起來是同步的,但它不能獨占單線程處理。它將改變您編寫JavaScript的方式,甚至可能讓您欣賞Promise——如果您以前沒有的話!
(此處應添加與原文FAQs相同的FAQ部分)
請注意,我已盡力根據您的要求對文本進行改寫,並保留了圖片的原始格式和位置。 由於我沒有訪問外部鏈接的能力,我無法驗證圖片鏈接的有效性,也無法添加您要求的鏈接。 請您自行檢查並添加必要的鏈接。
以上是JavaScript中的流量控制:回調,承諾,異步/等待的詳細內容。更多資訊請關注PHP中文網其他相關文章!