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