或許你會問,這個操作明明是同步的,定義 Promise 里面的代碼都被立即執(zhí)行了,那么回調(diào)應(yīng)該緊接著 resolve 函數(shù)執(zhí)行,那么應(yīng)該先打印 “resolved” 而不應(yīng)該先打印 “end Promise”.
這個是 Promise 規(guī)范規(guī)定的,為了防止同步調(diào)用和異步調(diào)用同時存在導(dǎo)致的混亂
6、Promise 的鏈?zhǔn)秸{(diào)用(連貫操作)
前面我們講過,Promise 的 then 方法以及 catch 方法返回的都是新的 Promise 對象,這樣我們可以非常方便的解決嵌套的回調(diào)函數(shù)的問題, 也可以很方便的實(shí)現(xiàn)流程任務(wù)。
var p = new Promise(function(resolve, reject) { resolve(); }); function taskA() { console.log("Task A"); } function taskB() { console.log("Task B"); } function taskC() { console.log("Task C"); } p.then(taskA()) .then(taskB()) .then(taskC()) .catch(function(error) { console.log(error); });
上面這段代碼很方便的實(shí)現(xiàn)了從 taskA 到 taskC 的有序執(zhí)行。
當(dāng)然你可以把 taskA - taskC 換成任何異步操作,如從后臺獲取數(shù)據(jù):
var getJSON = function(url, param) { var promise = new Promise(function(resolve, reject){ var request = require('ajax-request'); request({url:url, data: param}, function(err, res, body) { if (!err && res.statusCode == 200) { resolve(body); } else { reject(new Error(err)); } }); }); return promise; }; var url = "login.php"; getJSON(url, {id:1}).then(result => { console.log(result); return getJSON(url, {id:2}) }).then(result => { console.log(result); return getJSON(url, {id:3}); }).then(result => { console.log(result); }).catch(error => console.log(error));
這樣用起來似乎很爽,但是有個問題需要注意,我們說過每個 then() 方法都返回一個新的 Promise 對象,那既然是 Promise 對象,那肯定就有注冊 onFulfilled 和 onRejected, 如果某個任務(wù)流程的 then() 方法鏈過長的話,前面的任務(wù)拋出異常,會導(dǎo)致后面的任務(wù)被跳過。
function taskA() { console.log("Task A"); throw new Error("throw Error @ Task A"); } function taskB() { console.log("Task B"); } function onRejected(error) { console.log(error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask);
執(zhí)行的結(jié)果是:
Task A
Error: throw Error @ Task A
Final Task
顯然, 由于 A 任務(wù)拋出異常(執(zhí)行失敗),導(dǎo)致 .then(taskB) 被跳過,直接進(jìn)入 .catch 異常處理環(huán)節(jié)。
6.1 promise chain 中如何傳遞參數(shù)
上面我們簡單闡述了 Promise 的鏈?zhǔn)秸{(diào)用,能夠非常有效的處理異步的流程任務(wù)。
但是在實(shí)際的使用場景中,任務(wù)之間通常都是有關(guān)聯(lián)的,比如 taskB 需要依賴 taskA 的處理結(jié)果來執(zhí)行,這有點(diǎn)類似 Linux 管道機(jī)制。 Promise 中處理這個問題也很簡單,那就是在 taskA 中 return 的返回值,會在 taskB 執(zhí)行時傳給它。
function taskA() { console.log("Task A"); return "From Task A"; } function taskB(value) { console.log(value); console.log("Task B"); return "From Task B"; } function onRejected(error) { console.log(error); } function finalTask(value) { console.log(value); console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask);
搞定,就這么簡單!
6.2 resolve 和 reject 參數(shù)
reject函數(shù)的參數(shù)通常是Error對象的實(shí)例,表示拋出的錯誤;resolve函數(shù)的參數(shù)除了正常的值以外,還可能是另一個 Promise 實(shí)例, 比如像上面的 getJSON() 方法一樣。
var p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000) }) var p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000) }) p2 .then(result => console.log(result)) .catch(error => console.log(error))
注意,這時p1的狀態(tài)就會傳遞給p2,也就是說,p1的狀態(tài)決定了p2的狀態(tài)。
如果p1的狀態(tài)是 pending,那么p2的回調(diào)函數(shù)就會等待p1的狀態(tài)改變;
如果p1的狀態(tài)已經(jīng)是 resolved 或者 rejected,那么p2的回調(diào)函數(shù)將會立刻執(zhí)行。
7、Promise 基本方法
ES6的Promise API提供的方法不是很多,下面介紹一下 Promise 對象常用的幾個方法.
7.1 Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的別名,用于指定發(fā)生錯誤時的回調(diào)函數(shù)。
p.then((val) => console.log('fulfilled:', val)) .catch((err) => console.log('rejected', err)); // 等同于 p.then((val) => console.log('fulfilled:', val)) .then(null, (err) => console.log("rejected:", err));
Promise 對象的錯誤具有“冒泡”性質(zhì),會一直向后傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲。 所以通常建議使用 catch 方法去捕獲異常,而不要用 then(null, function(error) {}) 的方式,因?yàn)檫@樣只能捕獲當(dāng)前 Promise 的異常
p.then(result => {console.log(result)}) .then(result => {console.log(result)}) .then(result => {console.log(result)}) .catch(error => { //捕獲上面三個 Promise 對象產(chǎn)生的異常 console.log(error); });
跟傳統(tǒng)的try/catch代碼塊不同的是,如果沒有使用catch方法指定錯誤處理的回調(diào)函數(shù),Promise 對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應(yīng)。
通俗的說法就是“Promise 會吃掉錯誤”。
比如下面的代碼就出現(xiàn)這種情況
var p = new Promise(function(resolve, reject) { // 下面一行會報錯,因?yàn)閤沒有聲明 resolve(x + 2); }); p.then(() => {console.log("every thing is ok.");}); // 這行代碼會正常執(zhí)行,不會受 Promise 里面報錯的影響 console.log("Ok, it's Great.");
7.2 Promise.all()
Promise.all方法用于將多個 Promise 實(shí)例,包裝成一個新的 Promise 實(shí)例。用來處理組合 Promise 的邏輯操作。
var p = Promise.all([p1, p2, p3]);
上面代碼 p 的狀態(tài)由p1、p2、p3決定,分成兩種情況。
下面是一個具體的例子
// 生成一個Promise對象的數(shù)組 var promises = [1,2,3,4,5,6].map(function (id) { return getJSON('/post/' + id + ".json"); }); Promise.all(promises).then(function (posts) { // ... }).catch(function(reason){ // ... });
上面代碼中,promises 是包含6個 Promise 實(shí)例的數(shù)組,只有這6個實(shí)例的狀態(tài)都變成 fulfilled,或者其中有一個變?yōu)?rejected, 才會調(diào)用 Promise.all 方法后面的回調(diào)函數(shù)。
7.3 Promise.race()
Promise.race方法同樣是將多個Promise實(shí)例,包裝成一個新的Promise實(shí)例。 與 Promise.all 不同的是,只要有一個 promise 對象進(jìn)入 FulFilled 或者 Rejected 狀態(tài)的話,Promise.rece 就會繼續(xù)進(jìn)行后面的處理
var p = Promise.race([p1, p2, p3]);
上面代碼中,只要p1、p2、p3之中有一個實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個率先改變的 Promise 實(shí)例的返回值,就傳遞給p的回調(diào)函數(shù)。 Promise.race 方法的參數(shù)與 Promise.all 方法一樣,如果不是 Promise 實(shí)例,就會先調(diào)用 Promise.resolve 方法, 將參數(shù)轉(zhuǎn)為 Promise 實(shí)例,再進(jìn)一步處理。
下面是一個具體的例子
// `delay`毫秒后執(zhí)行resolve function timerPromisefy(delay) { return new Promise(function (resolve) { setTimeout(function () { resolve(delay); }, delay); }); } // 任何一個promise變?yōu)閞esolve或reject 的話程序就停止運(yùn)行 Promise.race([ timerPromisefy(1), timerPromisefy(32), timerPromisefy(64), timerPromisefy(128) ]).then(function (value) { console.log(value); // => 1 });
7.4 Promise.resolve()
Promise.resolve 方法有2個作用,一個就是前面我們說的,它是通過靜態(tài)方法創(chuàng)建 Promise 實(shí)例的渠道之一, 另一個作用就是將 Thenable 對象轉(zhuǎn)換為 Promise 對象。
那么什么是 Thenable 對象呢?ES6 Promises里提到了Thenable這個概念,簡單來說它就是一個非常類似promise的東西。 就像我們有時稱具有 .length 方法的非數(shù)組對象為 Array like 一樣,Thenable 指的是一個具有 .then 方法的對象。
這種將 Thenable對象轉(zhuǎn)換為 Promise 對象的機(jī)制要求thenable對象所擁有的 then 方法應(yīng)該和Promise所擁有的 then 方法具有同樣的功能和處理過程, 在將 Thenable 對象轉(zhuǎn)換為 Promise 對象的時候,還會巧妙的利用 Thenable 對象原來具有的 then 方法。
到底什么樣的對象能算是 Thenable 的呢,最簡單的例子就是 jQuery.ajax(),它的返回值就是 Thenable 的。
將 Thenable 對象轉(zhuǎn)換為 Promise 對象
var promise = Promise.resolve($.ajax('/json/comment.json'));// => promise對象 promise.then(function(value){ console.log(value); });
除了上面的方法之外,Promise.resolve方法的參數(shù)還有以下三種情況。
(1). 參數(shù)是一個 Promise 實(shí)例
如果參數(shù)是Promise實(shí)例,那么Promise.resolve將不做任何修改、原封不動地返回這個實(shí)例。
(2). 參數(shù)不是具有then方法的對象,或根本就不是對象
如果參數(shù)是一個原始值,或者是一個不具有then方法的對象,則Promise.resolve方法返回一個新的Promise對象,狀態(tài)為resolved。
var p = Promise.resolve('Hello'); p.then(function (s){ console.log(s) });
上面代碼生成一個新的Promise對象的實(shí)例p。由于字符串Hello不屬于異步操作(判斷方法是字符串對象不具有then方法), 返回Promise實(shí)例的狀態(tài)從一生成就是resolved,所以回調(diào)函數(shù)會立即執(zhí)行。 Promise.resolve方法的參數(shù),會同時傳給回調(diào)函數(shù)。
(3). 不帶有任何參數(shù)
Promise.resolve方法允許調(diào)用時不帶參數(shù),直接返回一個resolved狀態(tài)的Promise對象。這個我們在上面講創(chuàng)建 Promise 實(shí)例的三種方法的時候就講過了
var p = Promise.resolve(); p.then(function () { // ... });
7.5 Promise.reject()
Promise.reject(reason)方法也會返回一個新的 Promise 實(shí)例,該實(shí)例的狀態(tài)為rejected。 需要注意的是,Promise.reject()方法的參數(shù),會原封不動地作為 reject 的理由,變成后續(xù)方法的參數(shù)。這一點(diǎn)與 Promise.resolve 方法不一致。
const thenable = { then(resolve, reject) { reject('出錯了'); } }; Promise.reject(thenable) .catch(e => { console.log(e === thenable) }) // true
上面代碼中,Promise.reject 方法的參數(shù)是一個 thenable 對象,執(zhí)行以后,后面 catch 方法的參數(shù)不是 reject 拋出的“出錯了”這個字符串, 而是 thenable 對象。
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com