摘要:控制臺將顯示回調地獄通常,回調只能由一個異步函數調用。更多資源使更友好規范使用異步函數簡化異步編碼旅程異步編程是一項在中無法避免的挑戰。
JavaScript經常聲稱是_異步_。那是什么意思?它如何影響發展?近年來這種方法有何變化?
請思考以下代碼:
result1 = doSomething1(); result2 = doSomething2(result1);
大多數語言都處理每一行同步。第一行運行并返回結果。第二行在第一行完成后運行無論需要多長時間。
單線程處理JavaScript在單個處理線程上運行。在瀏覽器選項卡中執行時,其他所有內容都會停止,因為在并行線程上不會發生對頁面DOM的更改;將一個線程重定向到另一個URL而另一個線程嘗試追加子節點是危險的。
這對用戶來說是顯而易見。例如,JavaScript檢測到按鈕單擊,運行計算并更新DOM。完成后,瀏覽器可以自由處理隊列中的下一個項目。
(旁注:其他語言如PHP也使用單個線程,但可以由多線程服務器(如Apache)管理。同時對同一個PHP運行時頁面的兩個請求可以啟動兩個運行隔離的實例的線程。)
使用回調進行異步單線程引發了一個問題。當JavaScript調用“慢”進程(例如瀏覽器中的Ajax請求或服務器上的數據庫操作)時會發生什么?這個操作可能需要幾秒鐘 - 甚至幾分鐘。瀏覽器在等待響應時會被鎖定。在服務器上,Node.js應用程序將無法進一步處理用戶請求。
解決方案是異步處理。而不是等待完成,一個進程被告知在結果準備好時調用另一個函數。這稱為callback,它作為參數傳遞給任何異步函數。例如:
doSomethingAsync(callback1); console.log("finished"); // call when doSomethingAsync completes function callback1(error) { if (!error) console.log("doSomethingAsync complete"); }
doSomethingAsync()接受一個回調函數作為參數(只傳遞對該函數的引用,因此幾乎沒有開銷)。doSomethingAsync()需要多長時間并不重要;我們所知道的是callback1()將在未來的某個時刻執行。控制臺將顯示:
finished doSomethingAsync complete回調地獄
通常,回調只能由一個異步函數調用。因此可以使用簡潔的匿名內聯函數:
doSomethingAsync(error => { if (!error) console.log("doSomethingAsync complete"); });
通過嵌套回調函數,可以串行完成一系列兩個或多個異步調用。例如:
async1((err, res) => { if (!err) async2(res, (err, res) => { if (!err) async3(res, (err, res) => { console.log("async1, async2, async3 complete."); }); }); });
不幸的是,這引入了回調地獄 - 一個臭名昭著的概念(http://callbackhell.com/) !代碼難以閱讀,并且在添加錯誤處理邏輯時會變得更糟。
回調地獄在客戶端編碼中相對較少。如果您正在進行Ajax調用,更新DOM并等待動畫完成,它可以深入兩到三個級別,但它通常仍然可以管理。
操作系統或服務器進程的情況不同。Node.js API調用可以接收文件上載,更新多個數據庫表,寫入日志,并在發送響應之前進行進一步的API調用。
PromisesES2015(ES6)推出了Promises。回調仍然可以使用,但Promises提供了更清晰的語法chains異步命令,因此它們可以串行運行(更多相關內容)。
要啟用基于Promise的執行,必須更改基于異步回調的函數,以便它們立即返回Promise對象。該promises對象在將來的某個時刻運行兩個函數之一(作為參數傳遞):
resolve :處理成功完成時運行的回調函數
reject :發生故障時運行的可選回調函數。
在下面的示例中,數據庫API提供了一個接受回調函數的connect()方法。外部asyncDBconnect()函數立即返回一個新的Promise,并在建立連接或失敗后運行resolve()或reject():
const db = require("database"); // connect to database function asyncDBconnect(param) { return new Promise((resolve, reject) => { db.connect(param, (err, connection) => { if (err) reject(err); else resolve(connection); }); }); }
Node.js 8.0+提供了util.promisify()實用程序,將基于回調的函數轉換為基于Promise的替代方法。有幾個條件:
將回調作為最后一個參數傳遞給異步函數
回調函數必須指向一個錯誤,后跟一個值參數。
例子:
// Node.js: promisify fs.readFile const util = require("util"), fs = require("fs"), readFileAsync = util.promisify(fs.readFile); readFileAsync("file.txt");
各種客戶端庫也提供promisify選項,但您可以自己創建幾個:
// promisify a callback function passed as the last parameter // the callback function must accept (err, data) parameters function promisify(fn) { return function() { return new Promise( (resolve, reject) => fn( ...Array.from(arguments), (err, data) => err ? reject(err) : resolve(data) ) ); } } // example function wait(time, callback) { setTimeout(() => { callback(null, "done"); }, time); } const asyncWait = promisify(wait); ayscWait(1000);異步鏈
任何返回Promise的東西都可以啟動.then()方法中定義的一系列異步函數調用。每個都傳遞了上一個解決方案的結果:
asyncDBconnect("http://localhost:1234") .then(asyncGetSession) // passed result of asyncDBconnect .then(asyncGetUser) // passed result of asyncGetSession .then(asyncLogAccess) // passed result of asyncGetUser .then(result => { // non-asynchronous function console.log("complete"); // (passed result of asyncLogAccess) return result; // (result passed to next .then()) }) .catch(err => { // called on any reject console.log("error", err); });
同步函數也可以在.then()塊中執行。返回的值將傳遞給下一個.then()(如果有)。
.catch()方法定義了在觸發任何先前拒絕時調用的函數。此時,不會再運行.then()方法。您可以在整個鏈中使用多個.catch()方法來捕獲不同的錯誤。
ES2018引入了一個.finally()方法,無論結果如何都運行任何最終邏輯 - 例如,清理,關閉數據庫連接等。目前僅支持Chrome和Firefox,但技術委員會39已發布了 .finally() polyfill.
function doSomething() { doSomething1() .then(doSomething2) .then(doSomething3) .catch(err => { console.log(err); }) .finally(() => { // tidy-up here! }); }使用Promise.all()進行多個異步調用
Promise .then()方法一個接一個地運行異步函數。如果順序無關緊要 - 例如,初始化不相關的組件 - 同時啟動所有異步函數并在最后(最慢)函數運行解析時結束更快。
這可以通過Promise.all()來實現。它接受一組函數并返回另一個Promise。例如:
Promise.all([ async1, async2, async3 ]) .then(values => { // array of resolved values console.log(values); // (in same order as function array) return values; }) .catch(err => { // called on any reject console.log("error", err); });
如果任何一個異步函數調用失敗,則Promise.all()立即終止。
使用Promise.race的多個異步調用()Promise.race()與Promise.all()類似,只是它會在first Promise解析或拒絕后立即解析或拒絕。只有最快的基于Promise的異步函數才能完成:
Promise.race([ async1, async2, async3 ]) .then(value => { // single value console.log(value); return value; }) .catch(err => { // called on any reject console.log("error", err); });但是有什么別的問題嗎?
Promises 減少了回調地獄但引入了別的問題。
教程經常沒有提到_整個Promise鏈是異步的。使用一系列promise的任何函數都應返回自己的Promise或在最終的.then(),. catch()或.finally()方法中運行回調函數。
學習基礎知識至關重要。
更多的關于Promises的資源:
MDN Promise文檔
JavaScript Promises: 簡介
JavaScript Promises …相關細節
Promises異步編程
Async/AwaitPromises 可能令人生畏,因此ES2017引入了async and await。 雖然它可能只是語法糖,它使Promise更完善,你可以完全避免.then()鏈。 考慮下面的基于Promise的示例:
function connect() { return new Promise((resolve, reject) => { asyncDBconnect("http://localhost:1234") .then(asyncGetSession) .then(asyncGetUser) .then(asyncLogAccess) .then(result => resolve(result)) .catch(err => reject(err)) }); } // run connect (self-executing function) (() => { connect(); .then(result => console.log(result)) .catch(err => console.log(err)) })();
用這個重寫一下async/await:
外部函數必須以async語句開頭
對異步的基于Promise的函數的調用必須在await之前,以確保在下一個命令執行之前完成處理。
async function connect() { try { const connection = await asyncDBconnect("http://localhost:1234"), session = await asyncGetSession(connection), user = await asyncGetUser(session), log = await asyncLogAccess(user); return log; } catch (e) { console.log("error", err); return null; } } // run connect (self-executing async function) (async () => { await connect(); })();
await有效地使每個調用看起來好像是同步的,而不是阻止JavaScript的單個處理線程。 此外,異步函數總是返回一個Promise,因此它們可以被其他異步函數調用。
async/await 代碼可能不會更短,但有相當大的好處:
1、語法更清晰。括號更少,錯誤更少。
2、調試更容易。可以在任何await語句上設置斷點。
3、錯誤處理更好。try / catch塊可以與同步代碼一樣使用。
4、支持很好。它在所有瀏覽器(IE和Opera Mini除外)和Node 7.6+中都得到了支持。
但是并非所有都是完美的......
切勿濫用async/awaitasync / await仍然依賴于Promises,它最終依賴于回調。你需要了解Promises是如何工作的,并且沒有Promise.all()和Promise.race()的直接等價物。并且不要忘記Promise.all(),它比使用一系列不相關的await命令更有效。
同步循環中的異步等待在某些時候,您將嘗試調用異步函數中的同步循環。例如:
async function process(array) { for (let i of array) { await doSomething(i); } }
它不會起作用。這也不會:
async function process(array) { array.forEach(async i => { await doSomething(i); }); }
循環本身保持同步,并且總是在它們的內部異步操作之前完成。
ES2018引入了異步迭代器,它與常規迭代器一樣,但next()方法返回Promise。因此,await關鍵字可以與for循環一起用于串行運行異步操作。例如:
async function process(array) { for await (let i of array) { doSomething(i); } }
但是,在實現異步迭代器之前,最好將數組項映射到異步函數并使用Promise.all()運行它們。例如:
const todo = ["a", "b", "c"], alltodo = todo.map(async (v, i) => { console.log("iteration", i); await processSomething(v); }); await Promise.all(alltodo);
這具有并行運行任務的好處,但是不可能將一次迭代的結果傳遞給另一次迭代,并且映射大型數組可能在性能消耗上是很昂貴。
try/catch 有哪些問題了?如果省略任何await失敗的try / catch,async函數將以靜默方式退出。如果您有一組很長的異步await命令,則可能需要多個try / catch塊。
一種替代方案是高階函數,它捕獲錯誤,因此try / catch塊變得不必要(thanks to @wesbos for the suggestion):
async function connect() { const connection = await asyncDBconnect("http://localhost:1234"), session = await asyncGetSession(connection), user = await asyncGetUser(session), log = await asyncLogAccess(user); return true; } // higher-order function to catch errors function catchErrors(fn) { return function (...args) { return fn(...args).catch(err => { console.log("ERROR", err); }); } } (async () => { await catchErrors(connect)(); })();
但是,在應用程序必須以與其他錯誤不同的方式對某些錯誤做出反應的情況下,此選項可能不實用。
盡管有一些陷阱,async / await是JavaScript的一個優雅補充。更多資源:
MDN async and await
Async functions – 使 promises 更友好
TC39 Async Functions 規范
使用異步函數簡化異步編碼
JavaScript 旅程異步編程是一項在JavaScript中無法避免的挑戰。回調在大多數應用程序中都是必不可少的,但它很容易陷入深層嵌套的函數中。
Promises 抽象回調,但有許多語法陷阱。 轉換現有函數可能是一件苦差事,而.then()鏈仍然看起來很混亂。
幸運的是,async / await提供了清晰度。代碼看起來是同步的,但它不能獨占單個處理線程。它將改變你編寫JavaScript的方式!
(譯者注:Craig Buckler講解JavaScript的文章都還不錯,基本是用一些比較通俗的語言和代碼事例講解了JavaScript的一些特性和一些語法可能出現的問題。感興趣的朋友可以看一下(https://www.sitepoint.com/aut...))
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/97005.html
摘要:控制臺將顯示回調地獄通常,回調只能由一個異步函數調用。更多資源使更友好規范使用異步函數簡化異步編碼旅程異步編程是一項在中無法避免的挑戰。 JavaScript經常聲稱是_異步_。那是什么意思?它如何影響發展?近年來這種方法有何變化? 請思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數語言都處理每...
摘要:控制臺將顯示回調地獄通常,回調只能由一個異步函數調用。更多資源使更友好規范使用異步函數簡化異步編碼旅程異步編程是一項在中無法避免的挑戰。 JavaScript經常聲稱是_異步_。那是什么意思?它如何影響發展?近年來這種方法有何變化? 請思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數語言都處理每...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:讓我們使用它從數組中返回一個值數組在中,我們可以這樣做,這是一種更簡單的方法最重要的部分是創建數組,該數組立即調用所有的我們在主函數中等待這些。所以在我們真正等待完成之前,主函數就退出了。 原文:https://pouchdb.com/2015/03/0... PouchDB最棘手的方面之一是它的API是異步的。在Stack Overflow、Github和IRC上,我看到了不少困惑的...
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
閱讀 3476·2021-09-22 15:02
閱讀 3523·2021-09-02 15:21
閱讀 2140·2019-08-30 15:55
閱讀 2788·2019-08-30 15:44
閱讀 789·2019-08-29 16:56
閱讀 2422·2019-08-23 18:22
閱讀 3348·2019-08-23 12:20
閱讀 3097·2019-08-23 11:28