JavaScript 是單線程的,這意味着任何兩句代碼都不能同時運行,它們得一個接一個來。在瀏覽器中,JavaScript 和其他任務共享一個線程,不同的瀏覽器略有差異,但大體上這些和 JavaScript 共享線程的任務包括重繪、更新樣式、用戶交互等,所有這些任務操作都會阻塞其他任務。
一、事件的不足
對於那些執行時間很長,並且長時間占用線程的代碼,我們通常使用異步來執行,但是又如何判斷其是否執行完畢或者失敗呢?我們通常使用事件監聽,但事件監聽只能監聽綁定之后發生的事件,但有可能你寫綁定事件代碼之前該事件就已經發生,這樣你就無法檢測。下面進行說明:
你應該會用事件加回調的辦法來處理這類情況:
var img1 = document.querySelector('.img-1'); img1.addEventListener('load', function() { // 啊哈圖片加載完成 }); img1.addEventListener('error', function() { // 哎喲出問題了 });
這樣加載圖片就不會占據線程。我們添加幾個監聽函數,請求圖片,然后 JavaScript 就停止運行了,直到觸發某個監聽函數。
上面的例子中唯一的問題是,事件有可能在我們綁定監聽器之前就已經發生,所以我們先要檢查圖片的“complete”屬性:
var img1 = document.querySelector('.img-1'); function loaded() { // 啊哈圖片加載完成 } if (img1.complete) { loaded(); } else { img1.addEventListener('load', loaded); } img1.addEventListener('error', function() { // 哎喲出問題了 });
這樣還不夠,如果在添加監聽函數之前圖片加載發生錯誤,我們的監聽函數還是白費,不幸的是 DOM 也沒有為這個需求提供解決辦法。而且,這還只是處理一張圖片的情況,如果有一堆圖片要處理那就更麻煩了。
事件不是萬金油
事件機制最適合處理同一個對象上反復發生的事情—— keyup、touchstart 等等。你不需要考慮當綁定監聽器之前所發生的事情,當碰到異步請求成功/失敗的時候,你想要的通常是這樣:
img1.callThisIfLoadedOrWhenLoaded(function() { // 加載完成 }).orIfFailedCallThis(function() { // 加載失敗 }); // 以及…… whenAllTheseHaveLoaded([img1, img2]).callThis(function() { // 全部加載完成 }).orIfSomeFailedCallThis(function() { // 一個或多個加載失敗 });
這就是 Promise。如果 HTML 圖片元素有一個“ready()”方法的話,我們就可以這樣:
img1.ready().then(function() { // 加載完成 }, function() { // 加載失敗 }); // 以及…… Promise.all([img1.ready(), img2.ready()]).then(function() { // 全部加載完成 }, function() { // 一個或多個加載失敗 });
基本上 Promise 還是有點像事件回調的,除了:
- 一個 Promise 只能成功或失敗一次,並且狀態無法改變(不能從成功變為失敗,反之亦然)
- 如果一個 Promise 成功或者失敗之后,你為其添加針對成功/失敗的回調,則相應的回調函數會立即執行
這些特性非常適合處理異步操作的成功/失敗情景,你無需再擔心事件發生的時間點,而只需對其做出響應。
二、promise使回調函數和異步操作徹底分離
看了上述所講,感覺promise和回調函數作用差不多,但對於多層嵌套的回調,在代碼組織上確實優雅很多。
網頁的交互越來越復雜,JavaScript 的異步操作也隨之越來越多。如常見的 ajax 請求,需要在請求完成時響應操作,請求通常是異步的,請求的過程中用戶還能進行其他的操作,不會對頁面進行阻塞,這種異步的交互效果對用戶來說是挺有友好的。但是對於開發者來說,要大量處理這種操作,就很不友好了。異步請求完成的操作必須預先定義在回調函數中,等到請求完成就必須調用這個函數。這種非線性的異步編程方式會讓開發者很不適應,同時也帶來了諸多的不便,增加了代碼的耦合度和復雜性,代碼的組織上也會很不優雅,大大降低了代碼的可維護性。情況再復雜點,如果一個操作要等到多個異步 ajax 請求的完成才能進行,就會出現回調函數嵌套的情況,如果需要嵌套好幾層,那你就只能自求多福了。
先看看下面這個常見的異步函數。
var showMsg = function(){
setTimeout(function(){
alert( ‘hello’ );
}, 5000 );
};
如果要給該函數添加回調,通常會這么干。
var showMsg = function( callback ){
setTimeout(function(){
alert( ‘hello’ );
// 此處添加回調
callback();
}, 5000 );
};
如果是使用 easy.js 的 Promise,添加回調的方法就會優雅多了,前提是需要將原函數封裝成一個 promise 實例。
var showMsg = function(){
// 構造promise實例
var promise = new E.Promise();
setTimeout(function(){
alert( ‘hello’ );
// 改變promise的狀態
promise.resolve( ‘done’ );
}, 5000 );
// 返回promise實例
return promise;
};
將一個普通的函數封裝成一個 promise 實例,有3個關鍵步驟,第一步是在函數內部構造一個 promise 實例,第二步是部署函數執行完去改變 promise 的狀態為已完成,第三步就是返回這個 promise 實例。每個 promise 實例都有3種狀態,分別為 pending(未完成)、resolved(已完成,成功)、rejected(已拒絕,失敗)。下面再來看看如何添加回調。
showMsg().then(function( str ){
// 回調添加到這里來了
callback( str );
});
這樣就將回調函數和原來的異步函數徹底的分離了,從代碼組織上看,優雅了很多。resolve 接受一個參數,該參數就可以輕松實現將數據傳送給使用 then 方法添加的回調中。
對於 ajax 請求,easy.js 直接將 ajax 方法封裝成了 promise 對象,可以直接添加 then 方法來回調。
E.ajax({
url : ‘test1.php’,
type : ‘GET’
})
then(function(){
// 添加請求成功的回調
}, function(){
// 添加請求失敗的回調
});
then 方法接受2個函數作為參數,第一個函數是已完成的回調,第二個就是已失敗的回調。
如果有上面提到的多個 ajax 請求的情況呢?那么就要用到 when 這個方法了。該方法可以接受多個 promise 實例作為參數。
var requests = E.when(E.ajax({
url : ‘test1.php’,
type : ‘GET’
}), E.ajax({
url : ‘test2.php’,
type : ‘GET’
}));
requests.then(function( arg1, arg2 ){
console.log( ‘success:’ + arg1[0] + arg2[0] );
}, function( arg1, arg2 ){
console.log( ‘failure:’ + arg1 + arg2 );
});
when 方法是將多個 promise 實例存到一個數組中,等到該數組的所有 promise 實例都是已完成狀態才去執行已完成的回調,一旦有一個實例是已拒絕的狀態,則立即執行已拒絕的回調。
三、promise中你可能不知道的事情
1.then()返回promise
// Exhibit A var p = new Promise(/*...*/); p.then(func1); p.then(func2);
// Exhibit B var p = new Promise(/*...*/); p.then(func1) .then(func2);
若func1執行錯誤,A情況下,func2會正常執行,但B情況下,func2不會執行
2.then()中的回調函數必須返回參數
3.只有上一層錯誤才能被拋出
// Exhibit A new Promise(function(resolve, reject) { resolve("hello world"); }) .then( function(str) { throw new Error("uh oh"); }, undefined ) .then( undefined, function(error) { alert(error); } );
// Exhibit B new Promise(function(resolve, reject) { resolve("hello world"); }) .then( function(str) { throw new Error("uh oh"); }, function(error) { alert(error); } );
A情況下錯誤會拋出,B情況下不會
4.錯誤如果沒有再次拋出,將被視作已經修復,會繼續執行then()
var p = new Promise(function(resolve, reject) { reject(new Error("pebkac")); }); p.then( undefined, function(error) { } ) .then( function(str) { alert("I am saved!"); }, function(error) { alert("Bad computer!"); } );
5.promises也可以被中途終止
var p = new Promise(/*...*/); p.then(function(str) { if(!loggedIn) { return new Promise(/*...*/); } }) .then(function(str) { alert("Done."); })
只要在then()中加入return new Promise(/*...*/);
6.promise中的resolve()函數不是立刻執行的
function runme() { var i = 0; new Promise(function(resolve) { resolve(); }) .then(function() { i += 2; }); alert(i); }
上述代碼執行結果不一定是2,因為你覺得resolve是同步的,會立刻執行。但是你錯了!promise規定所有調用都必須是異步,所以當執行到alert(i)時,i可能還沒被修改!
四、應用
復雜的異步代碼變得簡單了
OK,現在我們來寫點實際的代碼。假設我們想要:
- 顯示一個加載指示圖標
- 加載一篇小說的 JSON,包含小說名和每一章內容的 URL。
- 在頁面中填上小說名
- 加載所有章節正文
- 在頁面中添加章節正文
- 停止加載指示
……這個過程中如果發生什么錯誤了要通知用戶,並且把加載指示停掉,不然它就會不停轉下去,令人眼暈,或者搞壞界面什么的。
當然了,你不會用 JavaScript 去這么繁瑣地顯示一篇文章,直接輸出 HTML 要快得多,不過這個流程是非常典型的 API 請求模式:獲取多個數據,當它們全部完成之后再做一些事情。
首先,搞定從網絡加載數據的步驟:
將 Promise 用於 XMLHttpRequest
只要能保持向后兼容,現有 API 都會更新以支持 Promise,XMLHttpRequest
是重點考慮對象之一。不過現在我們先來寫個 GET 請求:
function get(url) { // 返回一個新的 Promise return new Promise(function(resolve, reject) { // 經典 XHR 操作 var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function() { // 當發生 404 等狀況的時候調用此函數 // 所以先檢查狀態碼 if (req.status == 200) { // 以響應文本為結果,完成此 Promise resolve(req.response); } else { // 否則就以狀態碼為結果否定掉此 Promise // (提供一個有意義的 Error 對象) reject(Error(req.statusText)); } }; // 網絡異常的處理方法 req.onerror = function() { reject(Error("Network Error")); }; // 發出請求 req.send(); }); }
現在可以調用它了:
get('story.json').then(function(response) { console.log("Success!", response); }, function(error) { console.error("Failed!", error); });
鏈式調用
“then”的故事還沒完,你可以把這些“then”串聯起來修改結果或者添加進行更多異步操作。
值的處理
你可以對結果做些修改然后返回一個新值:
var promise = new Promise(function(resolve, reject) { resolve(1); }); promise.then(function(val) { console.log(val); // 1 return val + 2; }).then(function(val) { console.log(val); // 3 });
回到前面的代碼:
get('story.json').then(function(response) { console.log("Success!", response); });
收到的響應是一個純文本的 JSON,我們可以修改 get 函數,設置 responseType
為 JSON 來指定服務器響應格式,也可以在 Promise 的世界里搞定這個問題:
get('story.json').then(function(response) { return JSON.parse(response); }).then(function(response) { console.log("Yey JSON!", response); });
既然 JSON.parse
只接收一個參數,並返回轉換后的結果,我們還可以再精簡一下:
get('story.json').then(JSON.parse).then(function(response) { console.log("Yey JSON!", response); });
事實上,我們可以把getJSON
函數寫得超級簡單:
function getJSON(url) { return get(url).then(JSON.parse); }
getJSON
會返回一個獲取 JSON 並加以解析的 Promise。
隊列的異步操作
你也可以把“then”串聯起來依次執行異步操作。
當你從“then”的回調函數返回的時候,這里有點小魔法。如果你返回一個值,它就會被傳給下一個“then”的回調;而如果你返回一個“類 Promise”的對象,則下一個“then”就會等待這個 Promise 明確結束(成功/失敗)才會執行。例如:
getJSON('story.json').then(function(story) { return getJSON(story.chapterUrls[0]); }).then(function(chapter1) { console.log("Got chapter 1!", chapter1); });
這里我們發起一個對“story.json”的異步請求,返回給我們更多 URL,然后我們會請求其中的第一個。Promise 開始首次顯現出相較事件回調的優越性了。你甚至可以寫一個抓取章節內容的獨立函數:
var storyPromise; function getChapter(i) { storyPromise = storyPromise || getJSON('story.json'); return storyPromise.then(function(story) { return getJSON(story.chapterUrls[i]); }) } // 用起來非常簡單: getChapter(0).then(function(chapter) { console.log(chapter); return getChapter(1); }).then(function(chapter) { console.log(chapter); });
我們一開始並不加載 story.json,直到第一次 getChapter
,而以后每次 getChapter
的時候都可以重用已經加載完成的 story Promise,所以 story.json 只需要請求一次。Promise 好棒!
錯誤處理
前面已經看到,“then”接受兩個參數,一個處理成功,一個處理失敗(或者說肯定和否定,按 Promise 術語):
get('story.json').then(function(response) { console.log("Success!", response); }, function(error) { console.log("Failed!", error); });
你還可以使用“catch”:
get('story.json').then(function(response) { console.log("Success!", response); }).catch(function(error) { console.log("Failed!", error); });
這里的 catch 並無任何特殊之處,只是 比then(undefined, func)
的語法更直觀一點而已。注意上面兩段代碼的行為不僅相同,后者相當於:
get('story.json').then(function(response) { console.log("Success!", response); }).then(undefined, function(error) { console.log("Failed!", error); });
差異不大,但意義非凡。Promise 被否定之后會跳轉到之后第一個配置了否定回調的 then(或 catch,一樣的)。對於 then(func1, func2)
來說,必會調用 func1
或func2
之一,但絕不會兩個都調用。而 then(func1).catch(func2)
這樣,如果 func1
返回否定的話 func2
也會被調用,因為他們是鏈式調用中獨立的兩個步驟。看下面這段代碼:
asyncThing1().then(function() { return asyncThing2(); }).then(function() { return asyncThing3(); }).catch(function(err) { return asyncRecovery1(); }).then(function() { return asyncThing4(); }, function(err) { return asyncRecovery2(); }).catch(function(err) { console.log("Don't worry about it"); }).then(function() { console.log("All done!"); });
這段流程非常像 JavaScript 的 try/catch 組合,“try”代碼塊中發生的錯誤會立即跳轉到“catch”代碼塊。這是上面那段代碼的流程圖(我最愛流程圖了):
綠線是肯定的 Promise 流程,紅線是否定的 Promise 流程。
JavaScript 異常和 Promise
Promise 的否定回調可以由 Promise.reject() 觸發,也可以由構造器回調中拋出的錯誤觸發:
var jsonPromise = new Promise(function(resolve, reject) { // 如果數據格式不對的話 JSON.parse 會拋出錯誤 // 可以作為隱性的否定結果: resolve(JSON.parse("This ain't JSON")); }); jsonPromise.then(function(data) { // 永遠不會發生: console.log("It worked!", data); }).catch(function(err) { // 這才是真相: console.log("It failed!", err); });
這意味着你可以把所有 Promise 相關操作都放在它的構造函數回調中進行,這樣發生任何錯誤都能捕捉到並且觸發 Promise 否定。
“then”回調中拋出的錯誤也一樣:
get('/').then(JSON.parse).then(function() { // This never happens, '/' is an HTML page, not JSON // so JSON.parse throws console.log("It worked!", data); }).catch(function(err) { // Instead, this happens: console.log("It failed!", err); });
實踐錯誤處理
回到我們的故事和章節,我們用 catch
來捕捉錯誤並顯示給用戶:
getJSON('story.json').then(function(story) { return getJSON(story.chapterUrls[0]); }).then(function(chapter1) { addHtmlToPage(chapter1.html); }).catch(function() { addTextToPage("Failed to show chapter"); }).then(function() { document.querySelector('.spinner').style.display = 'none'; });
如果請求 story.chapterUrls[0]
失敗(http 500 或者用戶掉線什么的)了,它會跳過之后所有針對成功的回調,包括 getJSON
中將響應解析為 JSON 的回調,和這里把第一張內容添加到頁面里的回調。JavaScript 的執行會進入 catch 回調。結果就是前面任何章節請求出錯,頁面上都會顯示“Failed to show chapter”。
和 JavaScript 的 try/catch 一樣,捕捉到錯誤之后,接下來的代碼會繼續執行,按計划把加載指示器給停掉。上面的代碼就是下面這段的非阻塞異步版:
try { var story = getJSONSync('story.json'); var chapter1 = getJSONSync(story.chapterUrls[0]); addHtmlToPage(chapter1.html); } catch (e) { addTextToPage("Failed to show chapter"); } document.querySelector('.spinner').style.display = 'none';
如果只是要捕捉異常做記錄輸出而不打算在用戶界面上對錯誤進行反饋的話,只要拋出 Error 就行了,這一步可以放在 getJSON
中:
function getJSON(url) { return get(url).then(JSON.parse).catch(function(err) { console.log("getJSON failed for", url, err); throw err; }); }
現在我們已經搞定第一章了,接下來搞定全部章節。
並行和串行 —— 魚與熊掌兼得
異步的思維方式並不符合直覺,如果你覺得起步困難,那就試試先寫個同步的方法,就像這個:
try { var story = getJSONSync('story.json'); addHtmlToPage(story.heading); story.chapterUrls.forEach(function(chapterUrl) { var chapter = getJSONSync(chapterUrl); addHtmlToPage(chapter.html); }); addTextToPage("All done"); } catch (err) { addTextToPage("Argh, broken: " + err.message); } document.querySelector('.spinner').style.display = 'none';
它執行起來完全正常!不過它是同步的,在加載內容時會卡住整個瀏覽器。要讓它異步工作的話,我們用 then 把它們一個接一個串起來:
getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); // TODO: 獲取並顯示 story.chapterUrls 中的每個 url }).then(function() { // 全部完成啦! addTextToPage("All done"); }).catch(function(err) { // 如果過程中有任何不對勁的地方 addTextToPage("Argh, broken: " + err.message); }).then(function() { // 無論如何要把 spinner 隱藏掉 document.querySelector('.spinner').style.display = 'none'; });
那么我們如何遍歷章節的 URL 並且依次請求?這樣是不行的:
story.chapterUrls.forEach(function(chapterUrl) { // Fetch chapter getJSON(chapterUrl).then(function(chapter) { // and add it to the page addHtmlToPage(chapter.html); }); });
“forEach” 沒有對異步操作的支持,所以我們的故事章節會按照它們加載完成的順序顯示,基本上《低俗小說》就是這么寫出來的。我們不寫低俗小說,所以得修正它:
創建序列
我們要把章節 URL 數組轉換成 Promise 的序列,還是用 then
:
// 從一個完成狀態的 Promise 開始 var sequence = Promise.resolve(); // 遍歷所有章節的 url story.chapterUrls.forEach(function(chapterUrl) { // 從 sequence 開始把操作接龍起來 sequence = sequence.then(function() { return getJSON(chapterUrl); }).then(function(chapter) { addHtmlToPage(chapter.html); }); });
這是我們第一次用到 Promise.resolve
,它會依據你傳的任何值返回一個 Promise。如果你傳給它一個類 Promise 對象(帶有 then
方法),它會生成一個帶有同樣肯定/否定回調的 Promise,基本上就是克隆。如果傳給它任何別的值,如Promise.resolve('Hello')
,它會創建一個以這個值為完成結果的 Promise,如果不傳任何值,則以 undefined 為完成結果。
還有一個對應的 Promise.reject(val)
,會創建以你傳入的參數(或 undefined)為否定結果的 Promise。
我們可以用 array.reduce
精簡一下上面的代碼:
// 遍歷所有章節的 url story.chapterUrls.reduce(function(sequence, chapterUrl) { // 從 sequence 開始把操作接龍起來 return sequence.then(function() { return getJSON(chapterUrl); }).then(function(chapter) { addHtmlToPage(chapter.html); }); }, Promise.resolve());
它和前面的例子功能一樣,但是不需要顯式聲明 sequence
變量。reduce
回調會依次應用在每個數組元素上,第一輪里的“sequence”是 Promise.resolve()
,之后的調用里“sequence”就是上次函數執行的的結果。array.reduce
非常適合用於把一組值歸並處理為一個值,正是我們現在對 Promise 的用法。
匯總上面的代碼:
getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); return story.chapterUrls.reduce(function(sequence, chapterUrl) { // 當前一個章節的 Promise 完成之后…… return sequence.then(function() { // ……獲取下一章 return getJSON(chapterUrl); }).then(function(chapter) { // 並添加到頁面 addHtmlToPage(chapter.html); }); }, Promise.resolve()); }).then(function() { // 現在全部完成了! addTextToPage("All done"); }).catch(function(err) { // 如果過程中發生任何錯誤 addTextToPage("Argh, broken: " + err.message); }).then(function() { // 保證 spinner 最終會隱藏 document.querySelector('.spinner').style.display = 'none'; });
查看代碼運行示例,前面的同步代碼改造成了完全異步的版本。我們還可以更進一步。現在頁面加載的效果是這樣:
瀏覽器很擅長同時加載多個文件,我們這種一個接一個下載章節的方法非常低效率。我們希望同時下載所有章節,全部完成后一次搞定,正好就有這么個 API:
Promise.all(arrayOfPromises).then(function(arrayOfResults) { //... });
Promise.all
接受一個 Promise 數組為參數,創建一個當所有 Promise 都完成之后就完成的 Promise,它的完成結果是一個數組,包含了所有先前傳入的那些 Promise 的完成結果,順序和將它們傳入的數組順序一致。
getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); // 接受一個 Promise 數組並等待他們全部完成 return Promise.all( // 把章節 URL 數組轉換成對應的 Promise 數組 story.chapterUrls.map(getJSON) ); }).then(function(chapters) { // 現在我們有了順序的章節 JSON,遍歷它們…… chapters.forEach(function(chapter) { // ……並添加到頁面中 addHtmlToPage(chapter.html); }); addTextToPage("All done"); }).catch(function(err) { // 捕獲過程中的任何錯誤 addTextToPage("Argh, broken: " + err.message); }).then(function() { document.querySelector('.spinner').style.display = 'none'; });
根據連接狀況,改進的代碼會比順序加載方式提速數秒(查看示例),甚至代碼行數也更少。章節加載完成的順序不確定,但它們顯示在頁面上的順序准確無誤。
然而這樣還是有提高空間。當第一章內容加載完畢我們可以立即填進頁面,這樣用戶可以在其他加載任務尚未完成之前就開始閱讀;當第三章到達的時候我們不動聲色,第二章也到達之后我們再把第二章和第三章內容填入頁面,以此類推。
為了達到這樣的效果,我們同時請求所有的章節內容,然后創建一個序列依次將其填入頁面:
getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); // 把章節 URL 數組轉換成對應的 Promise 數組 // 這樣就可以並行加載它們 return story.chapterUrls.map(getJSON) .reduce(function(sequence, chapterPromise) { // 使用 reduce 把這些 Promise 接龍 // 以及將章節內容添加到頁面 return sequence.then(function() { // 等待當前 sequence 中所有章節和本章節的數據到達 return chapterPromise; }).then(function(chapter) { addHtmlToPage(chapter.html); }); }, Promise.resolve()); }).then(function() { addTextToPage("All done"); }).catch(function(err) { // 捕獲過程中的任何錯誤 addTextToPage("Argh, broken: " + err.message); }).then(function() { document.querySelector('.spinner').style.display = 'none'; });
魚與熊掌兼得!加載所有內容的時間未變,但用戶可以更早看到第一章。
這個小例子中各部分章節加載差不多同時完成,逐章顯示的策略在章節內容很多的時候優勢將會更加顯著。
上面的代碼如果用 Node.js 風格的回調或者事件機制實現的話代碼量大約要翻一倍,更重要的是可讀性也不如此例。然而,Promise 的厲害不止於此,和其他 ES6 的新功能結合起來還能更加高效……
Promise API 參考
除非額外注明,Chrome、Opera 和 Firefox(nightly)均支持下列所有方法。這個 polyfill 則在所有瀏覽器內實現了同樣的接口。
靜態方法
-
Promise.resolve(promise);
-
返回一個 Promise(當且僅當
promise.constructor == Promise
) -
Promise.resolve(thenable);
- 從 thenable 對象創建一個新的 Promise。一個 thenable(類 Promise)對象是一個帶有“then”方法的對象。
-
Promise.resolve(obj);
- 創建一個以 obj 為肯定結果的 Promise。
-
Promise.reject(obj);
- 創建一個以 obj 為否定結果的 Promise。為了一致性和調試便利(如堆棧追蹤),obj 應該是一個 Error 實例對象。
-
Promise.all(array);
- 創建一個 Promise,當且僅當傳入數組中的所有 Promise 都肯定之后才肯定,如果遇到數組中的任何一個 Promise 以否定結束,則拋出否定結果。每個數組元素都會首先經過 Promise.resolve,所以數組可以包含類 Promise 對象或者其他對象。肯定結果是一個數組,包含傳入數組中每個 Promise 的肯定結果(且保持順序);否定結果是傳入數組中第一個遇到的否定結果。
-
Promise.race(array);
-
創建一個 Promise,當數組中的任意對象肯定時將其結果作為肯定結束,或者當數組中任意對象否定時將其結果作為否定結束。
備注:我不大確定這個接口是否有用,我更傾向於一個
Promise.all
的對立方法,僅當所有數組元素全部給出否定的時候才拋出否定結果
構造器
new Promise(function(resolve, reject) {});
-
resolve(thenable)
- 你的 Promise 將會根據這個 “thenable” 對象的結果而返回肯定/否定結果
-
resolve(obj)
-
你的 Promise 將會以
obj
作為肯定結果完成 -
reject(obj)
-
你的 Promise 將會以
obj
作為否定結果完成。出於一致性和調試(如棧追蹤)方便,obj
應該是一個 Error 對象的實例。構造器的回調函數中拋出的錯誤會被立即傳遞給reject()
。
實例方法
-
promise.then(onFulfilled, onRejected)
-
當 promise 以肯定結束時會調用
onFulfilled
。 當 promise 以否定結束時會調用onRejected
。 這兩個參數都是可選的,當任意一個未定義時,對它的調用會跳轉到 then 鏈的下一個onFulfilled
/onRejected
上。 這兩個回調函數均只接受一個參數,肯定結果或者否定原因。 當Promise.resolve
肯定結束之后,then
會返回一個新的 Promise,這個 Promise 相當於你從onFulfilled
/onRejected
中返回的值。如果回調中拋出任何錯誤,返回的 Promise 也會以此錯誤作為否定結果結束。 -
promise.catch(onRejected)
-
promise.then(undefined, onRejected)
的語法糖。