摘要:簽訂協(xié)議的兩方分別是異步接口和。在異步函數(shù)中,使用異常捕獲的方案,代替了的異常捕獲的方案。需要注意的是,在異步函數(shù)中使異步函數(shù)用時(shí)要使用,不然異步函會被同步執(zhí)行。
同步與異步
通常,代碼是由上往下依次執(zhí)行的。如果有多個(gè)任務(wù),就必需排隊(duì),前一個(gè)任務(wù)完成,后一個(gè)任務(wù)才會執(zhí)行。這種執(zhí)行模式稱之為: 同步(synchronous) 。新手容易把計(jì)算機(jī)用語中的同步,和日常用語中的同步弄混淆。如,“把文件同步到云端”中的同步,指的是“使...保持一致”。而在計(jì)算機(jī)中,同步指的是任務(wù)從上往下依次執(zhí)行的模式。比如:
例 1
A(); B(); C();
在上述代碼中,A、B、C 是三個(gè)不同的函數(shù),每個(gè)函數(shù)都是一個(gè)不相關(guān)的任務(wù)。在同步模式下,計(jì)算機(jī)會先執(zhí)行 A 任務(wù),再執(zhí)行 B 任務(wù),最后執(zhí)行 C 任務(wù)。在大部分情況,同步模式都沒問題。但是如果 B 任務(wù)是一個(gè)耗時(shí)很長網(wǎng)絡(luò)的請求,而 C 任務(wù)恰好是展現(xiàn)新頁面,B 與 C 沒有依賴關(guān)系。這就會導(dǎo)致網(wǎng)頁卡頓的現(xiàn)象。有一種解決方案,將 B 放在 C 后面去執(zhí)行,但唯一有些不足的是,B 的網(wǎng)絡(luò)請求會遲一些再發(fā)送。
還有另一種更完美解決方案,將 B 任務(wù)分成的兩個(gè)部分。一部分是,立即執(zhí)行網(wǎng)絡(luò)請求的任務(wù);另一部分是,在請求數(shù)據(jù)回來后執(zhí)行的任務(wù)。這種一部分在立即執(zhí)行,另一部分在未來執(zhí)行的模式稱為 異步(asynchronous) 。偽代碼如下:
例 2
A(); // 在現(xiàn)在發(fā)送請求 ajax("url1",function B() { // 在未來某個(gè)時(shí)刻執(zhí)行 }) C(); // 執(zhí)行順序 A => C => B
實(shí)際上,JavaScript 引擎先執(zhí)行了調(diào)用了瀏覽器的網(wǎng)絡(luò)請求接口的任務(wù)(一部分任務(wù)),再由瀏覽器發(fā)送網(wǎng)絡(luò)請求并監(jiān)聽請求返回(這個(gè)任務(wù)不由 JavaScript 引擎執(zhí)行,而是瀏覽器);等請求放回后,瀏覽器再通知 JavaScript 引擎,開始執(zhí)行回調(diào)函數(shù)中的任務(wù)(另一部分)。JavaScript 異步能力的本質(zhì)是瀏覽器或 Node 的多線程能力。
callback未來執(zhí)行的函數(shù)通常也叫 callback。使用 callback 的異步模式,解決了阻塞的問題,但是也帶了一些其他問題。在最開始,我們的函數(shù)是從上往下書寫的,也是從上往下執(zhí)行的,這非常符合我們的思維習(xí)慣,但是現(xiàn)在卻被 callback 打斷了!在上面一段代碼中,它跳過 B 任務(wù),先執(zhí)行了 C任務(wù)!這種異步“非線性”的代碼會比同步“線性”的代碼,更難閱讀,因此也更容易滋生 BUG。
試著判斷下面這段代碼的執(zhí)行順序,你會對“非線性”代碼比“線性”代碼更難以閱讀,體會更深。
例 3
A(); ajax("url1", function(){ B(); ajax("url2", function(){ C(); } D(); }); E(); // 下面是答案,你猜對了嗎? // A => E => B => D => C
在例 3 中,我們的閱讀代碼視線是 A => B => C => D => E ,但是執(zhí)行順序卻是 A => E => B => D => C 。從上往下執(zhí)行的順序被 Callback 打亂了,這就是非線性代碼帶來的糟糕之處。
上面的例子中,我們可以通過將 ajax 后面執(zhí)行的任務(wù) E 和 任務(wù) D 提前,來進(jìn)行代碼優(yōu)化。這種技巧在寫多重嵌套的代碼時(shí),是非常有用的。改進(jìn)后,如下。
例 4
A(); E(); ajax("url1", function(){ B(); D(); ajax("url2", function(){ C(); } }); // 稍作優(yōu)化,代碼更容易看懂 // A => E => B => D => C
在例 4 中,只有處理了成功回調(diào),并沒處理異?;卣{(diào)。接下來,把異常處理回調(diào)加上,再來討論代碼“線性”執(zhí)行的問題。
例 5
A(); ajax("url1", function(){ B(); ajax("url2", function(){ C(); },function(){ D(); }); },function(){ E(); });
例 5 中,加上異常處理回調(diào)后,url1 的成功回調(diào)函數(shù) B 和異?;卣{(diào)函數(shù) E,被分開了。這種“非線性”的情況又出現(xiàn)了。
在 node 中,為了解決的異常處理“非線性”的問題,制定了錯(cuò)誤優(yōu)先的策略。node 中 callback 的第一個(gè)參數(shù),專門用于判斷是否發(fā)生異常。
例 6
A(); get("url1", function(error){ if(error){ E(); }else { B(); get("url2", function(error){ if(error){ D(); }else{ C(); } }); } });
到此,callback 引起的“非線性”問題基本得到解決。遺憾的是,一旦嵌套層數(shù)多起來,閱讀起來還不是很方便。此外,callback 一旦出現(xiàn)異常,只能在當(dāng)前回調(diào)內(nèi)部處理異常,并沒有一個(gè)整體的異常觸底方案。
promise在 JavaScript 的異步進(jìn)化史中,涌現(xiàn)出一系列解決 callback 弊端的庫,而 Promise 成為了最終的勝者,并成功地被引入了 ES6 中。它將提供了一個(gè)更好的“線性”書寫方式,并解決了異步異常只能在當(dāng)前回調(diào)中捕獲的問題。
Promise 就像一個(gè)中介,它承諾會將一個(gè)可信任的異步結(jié)果返回。簽訂協(xié)議的兩方分別是異步接口和 callback。首先 Promise 和異步接口簽訂一個(gè)協(xié)議,成功時(shí),調(diào)用 resolve 函數(shù)通知 Promise,異常時(shí),調(diào)用 reject 通知 Promise。另一方面 Promise 和 callback 也簽訂一個(gè)協(xié)議,當(dāng)異步接口的 resolve 或 reject 被調(diào)用時(shí),由 Promise 返回可信任的值給 then 和 catch 中注冊的 callback。
一個(gè)最簡單的 promise 示例如下:
例 7
// 創(chuàng)建一個(gè) Promise 實(shí)例(異步接口和 Promise 簽訂協(xié)議) var promise = new Promise(function (resolve,reject) { ajax("url",resolve,reject); }); // 調(diào)用實(shí)例的 then catch 方法 (成功回調(diào)、異常回調(diào)與 Promise 簽訂協(xié)議) promise.then(function(value) { // success }).catch(function (error) { // error })
Promise 是個(gè)非常不錯(cuò)的中介,它只返回可信的信息給 callback。怎么理解可信的概念呢?準(zhǔn)確的講,就是 callback 一定會被異步調(diào)用,且只會調(diào)用一次。比如在使用第三方庫的時(shí)候,由于某些原因,(假的)“異步”接口不可靠,它執(zhí)行了同步代碼,而沒有進(jìn)入異步邏輯,如例 8。
例 8
var promise1 = new Promise(function (resolve) { // 由于某些原因?qū)е隆爱惒健苯涌?,被同步?zhí)行了 if (true ){ // 同步代碼 resolve("B"); } else { // 異步代碼 setTimeout(function(){ resolve("B"); },0) } }); // promise依舊會異步執(zhí)行 promise1.then(function(value){ console.log(value) }); console.log("A"); // A => B (先 A 后 B)
再比如,由于某些原因,異步接口不可靠,resolve 或 reject 被執(zhí)行了兩次。但 Promise 只會通知 callback ,第一次異步接口返回的結(jié)果。如例 9:
例 9
var promise2 = new Promise(function (resolve) { // resolve 被執(zhí)行了 2 次 setTimeout(function(){ resolve("第一次"); },0) setTimeout(function(){ resolve("第二次"); },0) }); // 但 callback 只會被調(diào)用一次, promise2.then(function(msg){ console.log(msg) // "第一次" console.log("A") }); // A (只有一個(gè))
介紹完 Promise 的特性后,來看看它如何利用鏈?zhǔn)秸{(diào)用,解決 callback 模式下,異步代碼可讀性的問題。鏈?zhǔn)秸{(diào)用指的是:函數(shù) return 一個(gè)可以繼續(xù)執(zhí)行的對象,該對象可以繼續(xù)調(diào)用,并且 return 另一個(gè)可以繼續(xù)執(zhí)行的對象,如此反復(fù)達(dá)到不斷調(diào)用的結(jié)果。如例 10:
例 10
// return 一個(gè)可以繼續(xù)執(zhí)行的 Promise 對象 var fetch = function(url){ return new Promise(function (resolve,reject) { ajax(url,resolve,reject); }); } A(); fetch("url1").then(function(){ B(); // 返回一個(gè)新的 Promise 實(shí)例 return fetch("url2"); }).catch(function(){ C(); // 異常的時(shí)候也可以返回一個(gè)新的 Promise 實(shí)例 return fetch("url2"); // 使用鏈?zhǔn)綄懛ㄕ{(diào)用這個(gè)新的 Promise 實(shí)例的 then 方法 }).then(function() { // 可以繼續(xù) return,也可以不繼續(xù) return,結(jié)束鏈?zhǔn)秸{(diào)用 D(); }) // A B C D (順序執(zhí)行)
如此反復(fù),不斷返回一個(gè) Promise 對象,使 Promise 擺脫了 callback 層層嵌套的問題和異步代碼“非線性”執(zhí)行的問題。
另外,Promise 還解決了一個(gè)難點(diǎn),callback 只能捕獲當(dāng)前錯(cuò)誤異常。Promise 和 callback 不同,每個(gè) callback 只能知道自己的報(bào)錯(cuò)情況,但 Promise 代理著所有的 callback,所有 callback 的報(bào)錯(cuò),都可以由 Promise 統(tǒng)一處理。所以,可以通過在最后設(shè)置一個(gè) catch 來捕獲之前未捕獲異常。
Promise 解決 callback 的異步調(diào)用問題,但 Promise 并沒有擺脫 callback,它只是將 callback 放到一個(gè)可以信任的中間機(jī)構(gòu),這個(gè)中間機(jī)構(gòu)去鏈接 callback 和異步接口。此外,鏈?zhǔn)秸{(diào)用的寫法并不是非常優(yōu)雅。接下來介紹的異步(async)函數(shù)方案,會給出一個(gè)更好的解決方案。
異步(async)函數(shù)異步(async)函數(shù)是 ES7 的一個(gè)新的特性,它結(jié)合了 Promise,讓我們擺脫 callback 的束縛,直接用“同步”方式,寫異步函數(shù)。注意,這里的同步指的是寫法同步,但實(shí)際依舊是異步執(zhí)行的。
聲明異步函數(shù),只需在普通函數(shù)前添加一個(gè)關(guān)鍵字 async 即可,如:
async function main(){}
在異步函數(shù)中,可以使用 await 關(guān)鍵字,表示等待后面表達(dá)式的執(zhí)行結(jié)果,再往下繼續(xù)執(zhí)行。表達(dá)式一般都是 Promise 實(shí)例。如,例 11:
例 11
var timer = function (delay) { return new Promise(function create(resolve,reject) { if(typeof delay !== "number"){ reject(new Error("type error")); } setTimeout(resolve,delay,"done"); }); } async function main{ var value = await timer(100); // 不會立刻執(zhí)行,等待 100ms 后才開始執(zhí)行 console.log(value); // done } main();
異步函數(shù)和普通函數(shù)的調(diào)用方式一樣,最先執(zhí)行 main() 函數(shù)。之后,會立即執(zhí)行 timer(100) 函數(shù)。等到( await )后面的 promise 函數(shù)( timer(100) )返回結(jié)果后,程序才會執(zhí)行下一行代碼。
異步函數(shù)和普通函數(shù)寫法基本類似,除了前面提到的聲明方式類似和調(diào)用方式一樣之外,它也可以使用 try...catch 來捕捉異常,也可以傳入?yún)?shù)。但在異步函數(shù)中使用 return 是沒有作用的,這和普通的 callback 函數(shù) return 沒有作用是一樣原因。callback 或者異步函數(shù)是多帶帶放在 JavaScript 棧(stack)中執(zhí)行的,這時(shí)同步代碼已經(jīng)執(zhí)行完畢。
在異步函數(shù)中,使用 try...catch 異常捕獲的方案,代替了 Promise catch 的異常捕獲的方案。示例如下:
例 12
async function main(delay){ try{ // timer 在例 11 中有過聲明 var value1 = await timer(delay); var value2 = await timer(""); var value3 = await timer(delay); }catch(err){ console.error(err); // Error: type error // at create (:5:14) // at timer ( :3:10) // at A ( :12:10) } } main(0);
更神奇的是,異步函數(shù)也遵循,“函數(shù)是第一公民”的準(zhǔn)則。也可以當(dāng)作值,傳入普通函數(shù)和異步函數(shù)中執(zhí)行。需要注意的是,在異步函數(shù)中使異步函數(shù)用時(shí)要使用 await,不然異步函會被同步執(zhí)行。例子如下:
例 12
async function doAsync(delay){ // timer 在例 11 中有過聲明 var value1 = await timer(delay); console.log("A") } async function main(main){ doAsync(0); console.log("B") } main(main); // B A
這個(gè)時(shí)候打印出來的值是 B A。說明 doAsync 函數(shù)中的 await timer(delay) 并被同步執(zhí)行了。如果要正確異步地執(zhí)行 doAsync 函數(shù),需要該函數(shù)之前添加 await 關(guān)鍵字,如下:
async function main(delay){ var value1 = await timer(delay); console.log("A") } async function doAsync(main){ await main(0); console.log("B") } doAsync(main); // A B
由于異步函數(shù)采用類同步的書寫方法,所以在處理多個(gè)并發(fā)請求,新手可能會像下面一樣書寫:
例 13
var fetch = function (url) { return new Promise(function (resolve,reject) { ajax(url,resolve,reject); }); } async function main(){ try{ var value1 = await fetch("url1"); var value2 = await fetch("url2"); conosle.log(value1,value2); }catch(err){ console.error(err) } } main();
但這樣會導(dǎo)致 url2 的請求必需等到 url1 的請求回來后才會發(fā)送。如果 url1 與 url2 沒有相互的依賴關(guān)系,將這兩個(gè)請求同時(shí)發(fā)送實(shí)現(xiàn)的效果會更好。
Promise.all 的方法,可以很好的處理并發(fā)請求。Promise.all 接受將多個(gè) Promise 實(shí)例為參數(shù),并將這些參數(shù)包裝成一個(gè)新的 Promise 實(shí)例。這樣,Promise.all 中所有的請求會第一時(shí)間發(fā)送出去;在所有的請求成功回來后才會觸發(fā) Promise.all 的 resolve 函數(shù);當(dāng)有一個(gè)請求失敗,則立即調(diào)用 Promise.all 的 reject 函數(shù)。
var fetch = function (url) { return new Promise(function (resolve, reject) { ajax(url, resolve, reject); }); } async function main(){ try{ var arrValue = await Promise.all[fetch("url1"),fetch("url2")]; conosle.log(arrValue[0], arrValue[1]); }catch(err){ console.error(err) } } main();
最后對異步函數(shù)的內(nèi)容做個(gè)小結(jié):
聲明: async function main(){}
異步函數(shù)邏輯:可以使用 await
調(diào)用: main()
捕獲異常: try...catch
傳入?yún)?shù): main("第一個(gè)參數(shù)")
return:不生效
異步函數(shù)作為參數(shù)傳入其他函數(shù):可以
處理并發(fā)邏輯:Promise.all
目前使用最新的 Chrome/node 已經(jīng)支持 ES7 異步函數(shù)的寫法了,另外也可以通過 Babel 以將異步函數(shù)轉(zhuǎn)義為 ES5 的語法執(zhí)行。大家可以自己動(dòng)手試試,使用異步函數(shù),用類同步的方式,書寫異步代碼。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90840.html
摘要:閉包利用的,其實(shí)就是作用域嵌套情況下,內(nèi)部作用域可以訪問外部作用域這一特性。之所以要將閉包和垃圾回收策略聯(lián)系在一起,是因?yàn)檫@涉及到閉包的一些重要特性,如變量暫時(shí)存儲和內(nèi)存泄漏。因?yàn)槟涿瘮?shù)回調(diào)的閉包實(shí)際引用的是變量,而非變量的值。 本文旨在總結(jié)在js學(xué)習(xí)過程中,對閉包的思考,理解,以及一些反思,如有不足,還請大家指教 閉包---closure 閉包是js中比較特殊的一個(gè)概念,其特殊之處...
摘要:但是的的出現(xiàn)碉堡的新朋友,我們可以輕松寫出同步風(fēng)格的代碼同時(shí)又擁有異步機(jī)制,可以說是目前最簡單,最優(yōu)雅,最佳的解決方案了。不敢說這一定是終極的解決方案,但確實(shí)是目前最優(yōu)雅的解決方案 一、異步解決方案的進(jìn)化史 JavaScript的異步操作一直是個(gè)麻煩事,所以不斷有人提出它的各種解決方案。可以追溯到最早的回調(diào)函數(shù)(ajax老朋友),到Promise(不算新的朋友),再到ES6的Gener...
摘要:作者珂珂滬江前端開發(fā)工程師本文為原創(chuàng)文章,有不當(dāng)之處歡迎指出。只對未來發(fā)生的事情做出兩種基本情況的應(yīng)對成功和失敗。在異步轉(zhuǎn)同步這條道路上,只是一個(gè)出彩的點(diǎn),他還尚有一些缺陷和不足,并不是我們最終的解決方案。 作者:珂珂 (滬江前端開發(fā)工程師)本文為原創(chuàng)文章,有不當(dāng)之處歡迎指出。轉(zhuǎn)載請標(biāo)明出處。 一個(gè)新事物的產(chǎn)生必然是有其歷史原因的。為了更好的以同步的方式寫異步的代碼,人們在JS上操碎了...
摘要:然而,臨近規(guī)范發(fā)布時(shí),有建議提及未來的版本號切換為編年制,比如用同來指代在年末前被定稿的所有版本??偟脕碚f就是版本號不再那么重要了,開始變得更像一個(gè)萬古長青的活標(biāo)準(zhǔn)。 你不知道的JS(下卷)ES6與之未來 第一章:ES的今與明 在你想深入這本書之前,你應(yīng)該對(在讀此書時(shí))JavaScript的最近標(biāo)準(zhǔn)掌握熟練,也就是ES5(專業(yè)來說是ES 5.1)。在此,我們決定全方面地談?wù)撽P(guān)于將近的...
摘要:異步編程一般用來調(diào)取接口拉數(shù)據(jù)。通過我描述的篇幅,就知道異步編程比同步編程麻煩許多。遠(yuǎn)古時(shí)期,異步編程是通過回調(diào)函數(shù)來解決的。 半理解系列--Promise的進(jìn)化史 學(xué)過js的都知道,程序有同步編程和異步編程之分,同步就好比流水線,一步一個(gè)腳印,做完上個(gè)任務(wù)才做下一個(gè),異步編程好比客服,客服接了一個(gè)電話,收到了一個(gè)任務(wù),然后把任務(wù)交給另外的人來處理,同時(shí),繼續(xù)接聽下一個(gè)電話,等到另外的...
閱讀 952·2021-11-16 11:45
閱讀 2153·2021-10-09 09:44
閱讀 1376·2019-08-30 14:03
閱讀 1163·2019-08-26 18:28
閱讀 3357·2019-08-26 13:50
閱讀 1751·2019-08-23 18:38
閱讀 3478·2019-08-23 18:22
閱讀 3635·2019-08-23 15:27