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