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