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