摘要:異步編程三座大山原型原型鏈作用域閉包同步異步。異步操作執(zhí)行完畢后,再執(zhí)行該回調(diào)函數(shù),確?;卣{(diào)在異步操作之后執(zhí)行?;卣{(diào)函數(shù)本身是我們約定俗成的一種叫法,我們定義它,但是并不會(huì)自己去執(zhí)行它,它最終被其他人執(zhí)行了。
JS異步編程
JS三座大山:原型原型鏈、作用域閉包、同步異步。
之前有寫(xiě)過(guò)自己對(duì)閉包的理解,今天來(lái)總結(jié)一下JS中的異步。
思考(案例來(lái)自stackoverflow):
function foo(){ var result; $ajax({ url:"...", success:function(response){ result=response; //return response;//tried this one as well } }); return result; } var result=foo();
初學(xué)異步的時(shí)候,這里是很容易錯(cuò)的地方,你想要獲取從服務(wù)器端返回的數(shù)據(jù),結(jié)果卻一直undefined。
分析:
JavaScript是單線程語(yǔ)言,但是js中有很多任務(wù)耗時(shí)比較長(zhǎng),比如ajax請(qǐng)求,如果都按照順序進(jìn)行,往往會(huì)出現(xiàn)瀏覽器無(wú)響應(yīng)的情況,所以就需要異步的形式。JS中所有的任務(wù)可以分為兩種:同步任務(wù)和異步任務(wù)。
同步任務(wù):在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù);
異步任務(wù):不進(jìn)入主線程,而進(jìn)入任務(wù)隊(duì)列中的任務(wù),只有任務(wù)隊(duì)列通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了,這個(gè)任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。
事件循環(huán)(Event Loop):只有執(zhí)行棧中的所有同步任務(wù)都執(zhí)行完畢,系統(tǒng)才會(huì)讀取任務(wù)隊(duì)列,看看里面的異步任務(wù)哪些可以執(zhí)行,然后那些對(duì)應(yīng)的異步任務(wù),結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開(kāi)始執(zhí)行。
異步的解決方案:
下面我們嘗試將上面代碼改正一下,幾種方法如下:
1.callback
function foo(callback){//定義函數(shù)的時(shí)候?qū)⒘硪粋€(gè)函數(shù)(回調(diào)函數(shù))作為參數(shù)傳入定義的函數(shù)中。 $ajax({ //... success:callback//異步操作執(zhí)行完畢后,再執(zhí)行該回調(diào)函數(shù),確?;卣{(diào)在異步操作之后執(zhí)行。 }); } function myCallback(result){ //... } foo(myCallback);
回調(diào)函數(shù)本身是我們約定俗成的一種叫法,我們定義它,但是并不會(huì)自己去執(zhí)行它,它最終被其他人執(zhí)行了。
優(yōu)點(diǎn):比較容易理解;
缺點(diǎn):1.高耦合,維護(hù)困難,回調(diào)地獄;2.每個(gè)任務(wù)只能指定一個(gè)回調(diào)函數(shù);3.如果幾個(gè)異步操作之間并沒(méi)有順序之分,同樣也要等待上一個(gè)操作執(zhí)行結(jié)束再進(jìn)行下一個(gè)操作。下圖回調(diào)地獄(圖片來(lái)自于新浪微博(@ruanyf)):
2.Promise
function ajax(url){ return new Promise(function(resolve,reject){ var xhr=new XMLHttpRequest(); xhr.onload=function(){ resolve(this.responseText); }; xhr.onerror=reject; xhr.open("GET",url); xhr.send(); }); } ajax("/echo/json") .then(function(result){...}) .then(function(){...}) .catch(function(){...});
ES6給我們提供了一個(gè)原生的構(gòu)造函數(shù)Promise,Promise代表了一個(gè)異步操作,可以將異步對(duì)象和回調(diào)函數(shù)脫離開(kāi)來(lái),通過(guò).then方法在這個(gè)異步操作上綁定回調(diào)函數(shù),Promise可以讓我們通過(guò)鏈?zhǔn)秸{(diào)用的方法去解決回調(diào)嵌套的問(wèn)題,而且由于promise.all這樣的方法存在,可以讓同時(shí)執(zhí)行多個(gè)操作變得簡(jiǎn)單。
promise對(duì)象存在三種狀態(tài):
1)Fulfilled:成功狀態(tài)
2)Rejected:失敗狀態(tài)
3)Pending:既不是成功也不是失敗狀態(tài),可以理解為進(jìn)行中狀態(tài)
promise對(duì)象的兩個(gè)重要方法:resolve/reject
1)resolve方法可以使Promise對(duì)象的狀態(tài)改變?yōu)槌晒?,同時(shí)傳遞一個(gè)參數(shù)用于后續(xù)成功后的操作。
2)reject方法可以將Promise對(duì)象的狀態(tài)改變?yōu)槭?,同時(shí)將錯(cuò)誤信息傳遞到后續(xù)錯(cuò)誤處理的操作。
.then可以使用鏈?zhǔn)秸{(diào)用,原因在于:每一次執(zhí)行該方法時(shí)總會(huì)返回一個(gè)Promise對(duì)象。
另外,在then的函數(shù)當(dāng)中的返回值,可以作為后續(xù)操作的參數(shù)(例如:.then(return a).then(console.log(a+b)))
那么問(wèn)題來(lái)了,如果上面代碼異步操作拋出錯(cuò)誤,會(huì)怎么樣?會(huì)調(diào)用catch方法指定的回調(diào)函數(shù),處理這個(gè)錯(cuò)誤,而且then方法指定的回調(diào)函數(shù),如果運(yùn)行中拋出錯(cuò)誤,也會(huì)被catch捕獲。Promise對(duì)象的錯(cuò)誤具有“冒泡”性質(zhì),會(huì)一直向后傳遞,直到被捕獲為止,也就是說(shuō),錯(cuò)誤總是會(huì)被下一個(gè)catch語(yǔ)句捕獲。
理解Promise用法的關(guān)鍵點(diǎn):
1.then方法是Promise實(shí)例的方法,即Promise.prototype上的,它的作用是為Promise實(shí)例添加狀態(tài)改變時(shí)的回調(diào)函數(shù),這個(gè)方法的第一個(gè)參數(shù)是resolved狀態(tài)的回調(diào)函數(shù),第二個(gè)參數(shù)(可選)是rejected狀態(tài)的回調(diào)函數(shù)。
2.鏈?zhǔn)街械牡诙€(gè)then開(kāi)始,它們的resolve中的參數(shù),是前一個(gè)then中resolve的return語(yǔ)句的返回值。
3.關(guān)于執(zhí)行順序:Promise在實(shí)例化的時(shí)候就會(huì)執(zhí)行,也就是如果Promise的實(shí)例化語(yǔ)句中函數(shù)console.log輸出語(yǔ)句,它會(huì)比then中的先執(zhí)行。Promise.all中傳入的Promise對(duì)象的數(shù)組(假設(shè)為p1、p2),即使p2的運(yùn)行速度比p1快,Promise.all方法仍然會(huì)按照數(shù)組中的順序?qū)⒔Y(jié)果返回。
理解了上面這些方便寫(xiě)原生的Promise,利用觀察者模式。后面補(bǔ)充。
Promise的缺點(diǎn):
1.當(dāng)處于未完成狀態(tài)時(shí),無(wú)法確定目前處于哪一階段。
2.如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部的錯(cuò)誤不會(huì)反映到外部。
3.無(wú)法取消Promise,一旦新建它就會(huì)立即執(zhí)行,無(wú)法中途取消。
3.async/await:
很多人說(shuō)async/await是異步編程的終極解決方案、
JavaScript 的 async/await 實(shí)現(xiàn),離不開(kāi) Promise。
var superagent=require("superagent") function delay(){ return new Promise(function(resolve,reject){ setTimeout({ resolve(42); },3000); }) } async function getAllBooks(){ var bookIDs=await superagent.get("/user/books"); await delay(1000); return await superagent.get("/books/ids="JSON.stringify(bookIDs)); } getAllBooks() .then(function(){});
上面的 delay() 沒(méi)有申明為 async。實(shí)際上,delay() 本身就是返回的 Promise 對(duì)象,加不加 async 結(jié)果都一樣。
只要在函數(shù)名之前加上async關(guān)鍵字,就表明這個(gè)函數(shù)內(nèi)部有異步操作。這個(gè)異步操作返回一個(gè)Promise對(duì)象,前面用await關(guān)鍵字注明。函數(shù)執(zhí)行的時(shí)候,一旦遇到await,就會(huì)先執(zhí)行await后面的表達(dá)式中的內(nèi)容(異步),不再執(zhí)行函數(shù)體后面的語(yǔ)句。等到異步操作執(zhí)行完畢后,再自動(dòng)返回到函數(shù)體內(nèi),繼續(xù)執(zhí)行函數(shù)體后面的語(yǔ)句。
下面這段來(lái)自:https://segmentfault.com/a/11...
async:定義異步函數(shù)
1)自動(dòng)把函數(shù)轉(zhuǎn)換為Promise
2)當(dāng)調(diào)用異步函數(shù)時(shí),函數(shù)返回值會(huì)被resolve處理
3)異步函數(shù)內(nèi)部可以使用await
await:暫停異步函數(shù)的執(zhí)行
1)當(dāng)使用在Promise前面時(shí),await等待Promise完成,并返回Promise的結(jié)果
2)await只能和Promise一起使用,不能和callback一起使用
3)await只能用在async函數(shù)中
async/await并不會(huì)取代promise,因?yàn)閍sync/await底層依然使用promise。
async function getABC(){ let A = await getValueA(); // getValueA 花費(fèi) 2 秒 let B = await getValueB(); // getValueA 花費(fèi) 4 秒 let C = await getValueC(); // getValueA 花費(fèi) 3 秒 return A*B*C }
每次遇到?await?關(guān)鍵字時(shí),Promise 都會(huì)停下在,一直到運(yùn)行結(jié)束,所以總共花費(fèi)是 2+4+3 = 9 秒。await?把異步變成了同步。
async function getABC() { // Promise.all() 允許同時(shí)執(zhí)行所有的異步函數(shù) let results = await Promise.all([ getValueA, getValueB, getValueC ]); return results.reduce((total,value) => total * value); }
函數(shù)總耗時(shí)為 4 秒(getValueB?的耗時(shí))。
Async 的價(jià)值在于用寫(xiě)同步的方式寫(xiě)異步,1避免了阻塞,2必免寫(xiě)回調(diào)
async/await詳細(xì)了解,推薦:https://segmentfault.com/a/11...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107248.html
摘要:控制臺(tái)將顯示回調(diào)地獄通常,回調(diào)只能由一個(gè)異步函數(shù)調(diào)用。更多資源使更友好規(guī)范使用異步函數(shù)簡(jiǎn)化異步編碼旅程異步編程是一項(xiàng)在中無(wú)法避免的挑戰(zhàn)。 JavaScript經(jīng)常聲稱是_異步_。那是什么意思?它如何影響發(fā)展?近年來(lái)這種方法有何變化? 請(qǐng)思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數(shù)語(yǔ)言都處理每...
摘要:控制臺(tái)將顯示回調(diào)地獄通常,回調(diào)只能由一個(gè)異步函數(shù)調(diào)用。更多資源使更友好規(guī)范使用異步函數(shù)簡(jiǎn)化異步編碼旅程異步編程是一項(xiàng)在中無(wú)法避免的挑戰(zhàn)。 JavaScript經(jīng)常聲稱是_異步_。那是什么意思?它如何影響發(fā)展?近年來(lái)這種方法有何變化? 請(qǐng)思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數(shù)語(yǔ)言都處理每...
摘要:控制臺(tái)將顯示回調(diào)地獄通常,回調(diào)只能由一個(gè)異步函數(shù)調(diào)用。更多資源使更友好規(guī)范使用異步函數(shù)簡(jiǎn)化異步編碼旅程異步編程是一項(xiàng)在中無(wú)法避免的挑戰(zhàn)。 JavaScript經(jīng)常聲稱是_異步_。那是什么意思?它如何影響發(fā)展?近年來(lái)這種方法有何變化? 請(qǐng)思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數(shù)語(yǔ)言都處理每...
摘要:事件循環(huán)從回調(diào)隊(duì)列中獲取并將其推送到調(diào)用堆棧。如何工作請(qǐng)注意,不會(huì)自動(dòng)將您的回調(diào)函數(shù)放到事件循環(huán)隊(duì)列中。它設(shè)置了一個(gè)計(jì)時(shí)器,當(dāng)計(jì)時(shí)器到期時(shí),環(huán)境將您的回調(diào)函數(shù)放入事件循環(huán)中,以便將來(lái)的某個(gè)事件會(huì)將其選中并執(zhí)行它。 我們將通過(guò)回顧第一篇文章中單線程編程的缺點(diǎn),然后在討論如何克服它們來(lái)構(gòu)建令人驚嘆的JavaScript UI。在文章結(jié)尾處,我們將分享5個(gè)關(guān)于如何使用async / awai...
摘要:函數(shù)會(huì)在之后的某個(gè)時(shí)刻觸發(fā)事件定時(shí)器。事件循環(huán)中的這樣一次遍歷被稱為一個(gè)。執(zhí)行完畢并出棧。當(dāng)定時(shí)器過(guò)期,宿主環(huán)境會(huì)把回調(diào)函數(shù)添加至事件循環(huán)隊(duì)列中,然后,在未來(lái)的某個(gè)取出并執(zhí)行該事件。 原文請(qǐng)查閱這里,略有改動(dòng)。 本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。 這是 JavaScript 工作原理的第四章。 現(xiàn)在,我們將會(huì)通過(guò)回顧單線程環(huán)境下編程的弊端及如何克服這些困難以創(chuàng)建令人驚嘆...
閱讀 2227·2019-08-30 15:54
閱讀 1963·2019-08-30 13:49
閱讀 683·2019-08-29 18:44
閱讀 836·2019-08-29 18:39
閱讀 1119·2019-08-29 15:40
閱讀 1540·2019-08-29 12:56
閱讀 3155·2019-08-26 11:39
閱讀 3107·2019-08-26 11:37