摘要:第一個回調函數完成以后,會將返回結果作為參數,傳入第二個回調函數。捕獲錯誤方法是的別名,用于指定發(fā)生錯誤時的回調函數。處理前一個回調函數運行時發(fā)生的錯誤出錯啦對象的錯誤具有冒泡性質,會一直向后傳遞,直到被捕獲為止。
前言
一直想寫一篇關于promise的文來總結一下之前零零散散的promise知識點,趁著工作閑暇,來做個總結。PS:本文適合有一定JavaScript基礎的童鞋閱讀。
什么是promise如果說到JavaScript的異步處理,我想大多數人都會想到利用回調函數:
//示例:使用了回調函數的異步處理 http.get("/v1/get", function(error, data) { if (error) { //錯誤時的處理 } //成功時的處理 })
像上面這樣基于回調函數的異步處理如果統(tǒng)一參數使用規(guī)則的話,寫法也會很明了。但是,這也僅是編碼規(guī)約而已,即使采用不同的寫法也不會報錯。
而Promise則是把類似的異步處理對象和處理規(guī)則進行規(guī)范化,并按照采用統(tǒng)一的接口來編寫,而采取規(guī)定方法之外的寫法都會報錯。下面看一個例子:
var promise = http.get("/v1/get"); promise.then(function(result) { //成功時的處理 }).catch(function(error) { //錯誤時的處理 })
可以看到,這里在使用promise進行一步處理的時候,我們必須按照接口規(guī)定的方法編寫處理代碼。也就是說,除promise對象規(guī)定的方法(這里的 then 或 catch)以外的方法都是不可以使用的, 而不會像回調函數方式那樣可以自己自由的定義回調函數的參數,而必須嚴格遵守固定、統(tǒng)一的編程方式來編寫代碼。這樣,基于Promise的統(tǒng)一接口的做法, 就可以形成基于接口的各種各樣的異步處理模式。
但這并不是使用promise的足夠理由,promise為異步操作提供了統(tǒng)一的接口,能讓代碼不至于陷入回調嵌套的死路中,它的強大之處在于它的鏈式調用(文章后面會有提及)。
基本用法promise的語法:
new Promise(function(resolve, reject) { //待處理的異步邏輯 //處理結束后,調用resolve或reject方法 })
新建一個promise很簡單,只需要new一個promise對象即可。所以promise本質上就是一個函數,它接受一個函數作為參數,并且會返回promise對象,這就給鏈式調用提供了基礎。
其實Promise函數的使命,就是構建出它的實例,并且負責幫我們管理這些實例。而這些實例有以下三種狀態(tài):
pending: 初始狀態(tài),位履行或拒絕
fulfilled: 意味著操作成功完成
rejected: 意味著操作失敗
pending 狀態(tài)的 Promise對象可能以 fulfilled狀態(tài)返回了一個值,也可能被某種理由(異常信息)拒絕(reject)了。當其中任一種情況出現時,Promise 對象的 then 方法綁定的處理方法(handlers)就會被調用,then方法分別指定了resolve方法和reject方法的回調函數
一圖勝千言:
簡單的示例:
var promise = new Promise(function(resolve, reject) { if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } }); promise.then(function(value) { // 如果調用了resolve方法,執(zhí)行此函數 }, function(value) { // 如果調用了reject方法,執(zhí)行此函數 });
上述代碼很清晰的展示了promise對象運行的機制。下面再看一個示例:
var getJSON = function(url) { var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); function handler() { if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; }); return promise; }; getJSON("/posts.json").then(function(json) { console.log("Contents: " + json); }, function(error) { console.error("出錯了", error); });
上面代碼中,resolve方法和reject方法調用時,都帶有參數。它們的參數會被傳遞給回調函數。reject方法的參數通常是Error對象的實例,而resolve方法的參數除了正常的值以外,還可能是另一個Promise實例,比如像下面這樣。
var p1 = new Promise(function(resolve, reject){ // ... some code }); var p2 = new Promise(function(resolve, reject){ // ... some code resolve(p1); })
上面代碼中,p1和p2都是Promise的實例,但是p2的resolve方法將p1作為參數,這時p1的狀態(tài)就會傳遞給p2。如果調用的時候,p1的狀態(tài)是pending,那么p2的回調函數就會等待p1的狀態(tài)改變;如果p1的狀態(tài)已經是fulfilled或者rejected,那么p2的回調函數將會立刻執(zhí)行。
promise的鏈式操作正如前面提到的,Promise.prototype.then方法返回的是一個新的Promise對象,因此可以采用鏈式寫法。
getJSON("/visa.json").then(function(json) { return json.name; }).then(function(name) { // proceed });
上面的代碼使用then方法,依次指定了兩個回調函數。第一個回調函數完成以后,會將返回結果作為參數,傳入第二個回調函數。
如果前一個回調函數返回的是Promise對象,這時后一個回調函數就會等待該Promise對象有了運行結果,才會進一步調用。
getJSON("/visa/get.json").then(function(post) { return getJSON(post.jobURL); }).then(function(jobs) { // 對jobs進行處理 });
這種設計使得嵌套的異步操作,可以被很容易得改寫,從回調函數的“橫向發(fā)展”改為“向下發(fā)展”。
promise捕獲錯誤Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的別名,用于指定發(fā)生錯誤時的回調函數。
getJSON("/visa.json").then(function(result) { // some code }).catch(function(error) { // 處理前一個回調函數運行時發(fā)生的錯誤 console.log("出錯啦!", error); });
Promise對象的錯誤具有“冒泡”性質,會一直向后傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲。
getJSON("/visa.json").then(function(json) { return json.name; }).then(function(name) { // proceed }).catch(function(error) { //處理前面任一個then函數拋出的錯誤 });其他常用的promise方法 Promise.all方法,Promise.race方法
Promise.all方法用于將多個Promise實例,包裝成一個新的Promise實例。
var p = Promise.all([p1,p2,p3]);
上面代碼中,Promise.all方法接受一個數組作為參數,p1、p2、p3都是Promise對象的實例。(Promise.all方法的參數不一定是數組,但是必須具有iterator接口,且返回的每個成員都是Promise實例。)
p的狀態(tài)由p1、p2、p3決定,分成兩種情況。
只有p1、p2、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。
只要p1、p2、p3之中有一個被rejected,p的狀態(tài)就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
下面是一個具體的例子。
// 生成一個Promise對象的數組 var promises = [2, 3, 5, 7, 11, 13].map(function(id){ return getJSON("/get/addr" + id + ".json"); }); Promise.all(promises).then(function(posts) { // ... }).catch(function(reason){ // ... });
Promise.race方法同樣是將多個Promise實例,包裝成一個新的Promise實例。
var p = Promise.race([p1,p2,p3]);
上面代碼中,只要p1、p2、p3之中有一個實例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個率先改變的Promise實例的返回值,就傳遞給p的返回值。
如果Promise.all方法和Promise.race方法的參數,不是Promise實例,就會先調用下面講到的Promise.resolve方法,將參數轉為Promise實例,再進一步處理。
Promise.resolve方法,Promise.reject方法有時需要將現有對象轉為Promise對象,Promise.resolve方法就起到這個作用。
var jsPromise = Promise.resolve($.ajax("/whatever.json"));
上面代碼將jQuery生成deferred對象,轉為一個新的ES6的Promise對象。
如果Promise.resolve方法的參數,不是具有then方法的對象(又稱thenable對象),則返回一個新的Promise對象,且它的狀態(tài)為fulfilled。
var p = Promise.resolve("Hello"); p.then(function (s){ console.log(s) }); // Hello
上面代碼生成一個新的Promise對象的實例p,它的狀態(tài)為fulfilled,所以回調函數會立即執(zhí)行,Promise.resolve方法的參數就是回調函數的參數。
如果Promise.resolve方法的參數是一個Promise對象的實例,則會被原封不動地返回。
Promise.reject(reason)方法也會返回一個新的Promise實例,該實例的狀態(tài)為rejected。Promise.reject方法的參數reason,會被傳遞給實例的回調函數。
var p = Promise.reject("出錯啦"); p.then(null, function (error){ console.log(error) }); // 出錯了
上面代碼生成一個Promise對象的實例p,狀態(tài)為rejected,回調函數會立即執(zhí)行。
題外話:async函數async函數是es7提案出來的語法,并不屬于es6,但是已經有一些平臺和編輯器支持這種函數了,所以這里也做一下了解。
async函數是用來取代回調函數的另一種方法。
只要函數名之前加上async關鍵字,就表明該函數內部有異步操作。該異步操作應該返回一個Promise對象,前面用await關鍵字注明。當函數執(zhí)行的時候,一旦遇到await就會先返回,等到觸發(fā)的異步操作完成,再接著執(zhí)行函數體內后面的語句。
async function getStockPrice(symbol, currency) { let price = await getStockPrice(symbol); return convert(price, currency); }
上面代碼是一個獲取股票報價的函數,函數前面的async關鍵字,表明該函數將返回一個Promise對象。調用該函數時,當遇到await關鍵字,立即返回它后面的表達式(getStockPrice函數)產生的Promise對象,不再執(zhí)行函數體內后面的語句。等到getStockPrice完成,再自動回到函數體內,執(zhí)行剩下的語句。
下面是一個更一般性的例子。
function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncValue(value) { await timeout(50); return value; }
上面代碼中,asyncValue函數前面有async關鍵字,表明函數體內有異步操作。執(zhí)行的時候,遇到await語句就會先返回,等到timeout函數執(zhí)行完畢,再返回value。
個人覺得async函數將異步發(fā)揮到了極致,代碼看上去更加簡潔,更加舒服了,而且其流程也很好理解。
總結promise作為異步操作的規(guī)則,確實給開發(fā)帶來了不少便利,至少不用像回調那樣,出現函數里面套函數這種無限的嵌套的情況,promise讓你的代碼變得更加的優(yōu)雅了。當然如果之后async函數變得更加普及,那么就更好了。
下面來看看下面這道題目,大家可以思考下結果是多少?
console.log(1); new Promise(function (resolve, reject){ reject(true); window.setTimeout(function (){ resolve(false); }, 0); }).then(function(){ console.log(2); }, function(){ console.log(3); }); console.log(4);
答案我就不貼出來了,給大家思考的空間,實在不知道結果的也可以直接復制這段代碼到瀏覽器控制臺執(zhí)行下,也能很快的出結果。
展望很好奇promise內部的實現機理,接下來我會深入研究下promise實現原理,有成果了給大家分享哦~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83035.html
摘要:下一篇大概就是源碼方面的學習筆記了龜速學習中這一次我是去看了下規(guī)范照例傳送門圖靈社區(qū)規(guī)范首先吧個人總結下該用的詞解決結婚拒絕婉拒終值值傳家寶拒因好人卡等等異常車禍理下概念我們的的就像是一場姻緣對吧解決呢就是結婚成功啦傳家寶也如愿的傳給下一代 下一篇大概就是源碼方面的學習筆記了...龜速學習中... 這一次我是去看了下Promises/A+規(guī)范照例傳送門:圖靈社區(qū)Promises/A+規(guī)...
摘要:版本以及之前,本身還沒有異步執(zhí)行代碼的能力,宿主環(huán)境傳遞給引擎,然后按順序執(zhí)行,由宿主發(fā)起任務。采納引擎術語,把宿主發(fā)起的任務稱為宏觀任務,把引擎發(fā)起的任務稱為微觀任務。基本用法示例的回調是一個異步的執(zhí)行過程。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的...
摘要:版本以及之前,本身還沒有異步執(zhí)行代碼的能力,宿主環(huán)境傳遞給引擎,然后按順序執(zhí)行,由宿主發(fā)起任務。采納引擎術語,把宿主發(fā)起的任務稱為宏觀任務,把引擎發(fā)起的任務稱為微觀任務。基本用法示例的回調是一個異步的執(zhí)行過程。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的...
摘要:版本以及之前,本身還沒有異步執(zhí)行代碼的能力,宿主環(huán)境傳遞給引擎,然后按順序執(zhí)行,由宿主發(fā)起任務。采納引擎術語,把宿主發(fā)起的任務稱為宏觀任務,把引擎發(fā)起的任務稱為微觀任務。基本用法示例的回調是一個異步的執(zhí)行過程。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的...
摘要:異步操作未完成異步操作成功異步操作失敗基本用法是一個構造函數,接收一個參數,這個參數是函數,同時這個參數函數要傳入兩個參數,,分別表示異步操作執(zhí)行成功后的回調函數和異步操作執(zhí)行失敗后的回調函數。如果調用函數,就會調用方法的第一個參數。 Promise對象 Promise 表示一個異步操作的最終結果,與之進行交互的方式主要是 then 方法,該方法注冊了兩個回調函數,用于接收 promi...
閱讀 844·2023-04-25 21:21
閱讀 3226·2021-11-24 09:39
閱讀 3067·2021-09-02 15:41
閱讀 1995·2021-08-26 14:13
閱讀 1827·2019-08-30 11:18
閱讀 2768·2019-08-29 16:25
閱讀 507·2019-08-28 18:27
閱讀 1580·2019-08-28 18:17