編后吐槽:寫的快花眼,很詳細,耐心看必受益匪淺
JavaScript的執(zhí)行環(huán)境是「單線程」的。所謂單線程,是指JS引擎中負責解釋和執(zhí)行JavaScript代碼的線程只有一個,也就是一次只能完成一項任務(wù),這個任務(wù)執(zhí)行完后才能執(zhí)行下一個,它會「阻塞」其他任務(wù)。這個任務(wù)可稱為主線程。但實際上還有其他線程,如事件觸發(fā)線程、ajax請求線程等。因為javascript的單線程原理,使得網(wǎng)絡(luò)操作,瀏覽器事件,都必須是異步執(zhí)行的。
* 同步與異步
- 同步:同步模式,即上述所說的單線程模式,一次只能執(zhí)行一個任務(wù),函數(shù)調(diào)用后需等到函數(shù)執(zhí)行結(jié)束,返回執(zhí)行的結(jié)果,才能進行下一個任務(wù)。如果這個任務(wù)執(zhí)行的時間較長,就會導致「線程阻塞」。
var x = true;
while(x);
console.log("don‘t’ carry out");
// javascript 單線程原理導致同步執(zhí)行,第三步不被執(zhí)行
- 異步:可以一起執(zhí)行多個任務(wù),函數(shù)調(diào)用后不會立即返回執(zhí)行的結(jié)果,如果任務(wù)A需要等待,可先執(zhí)行任務(wù)B,等到任務(wù)A結(jié)果返回后再繼續(xù)回調(diào)。
// 常見異步,定時器的使用
setTimeout(function() {
console.log('taskA, run as asynchronous');
}, 0)
console.log('taskB, run as synchronize');
//while(true);
// taskB, run as synchronize
// taskA, run as asynchronous
?即使延時時長為0,taskA依舊晚于taskB;因為定時器是異步的,異步任務(wù)會在當前腳本所有同步任務(wù)完成之后才被執(zhí)行。,如果同步任務(wù)中含有阻塞任務(wù),即放開代碼中的while(true),那么taskA將不會被執(zhí)行。
* 什么是Promise
? 古人云:“君子一諾千金”,這種“承諾某個狀態(tài)將來會執(zhí)行,并且該狀態(tài)不會被改變”的對象在JavaScript中稱為Promise對象,他是一個構(gòu)造函數(shù),能將異步的操作以同步的形式表達出來,避免了嵌套地獄(層層嵌套)的發(fā)生。它供了統(tǒng)一的API,使得控制異步更加容易
* 回調(diào)函數(shù)
?回調(diào)函數(shù)是一段可執(zhí)行的代碼段,它以「參數(shù)」的形式傳遞給其他代碼,在其合適的時間執(zhí)行這段(回調(diào)函數(shù))的代碼。
?異步可以回調(diào),同步也可以回調(diào);
// 同步回調(diào)
var func1 = function(cb) {
// ..do something
console.log("before callback");
(cb && typeof(cb) === 'function') && cb();
console.log("after callback");
}
var func2 = function(param) {
// ..do something
var start = new Date();
if(( new Date() - start) < 3000 ){ } // 同步實現(xiàn)延時函數(shù)
console.log("I am callback");
}
func1(func2);
------output-------
// before callback
(after 3s...)
// I am callback
// after callback
?由于是同步回調(diào),會阻塞后面的代碼,如果func2是個死循環(huán),后面的代碼就不執(zhí)行了。為解決這個問題,我們使用異步回調(diào),除了常見的setTimeout,ajax也是應(yīng)用方式之一
// 異步回調(diào)
function request(url, param, successFun, errorFunc) {
$.ajax({
type: 'GET',
url: url,
param: param,
async: true, // 默認為true,如果設(shè)置為false則變成同步請求
success: successFunc,
error: errorFunc
});
}
request('test.html', '', function(data) {
// 第三個參數(shù)為返回成功時執(zhí)行的函數(shù)
console.log('cbData: ', data);
}, function(data) {
// 第四個參數(shù)為失敗時的函數(shù)
console.log('error: ', error);
});
* 為什么要用Promise
既然已經(jīng)可以實現(xiàn)異步回調(diào),那我們?yōu)槭裁催€要用Promise呢?
javascript代碼
function callback () {
console.log("Done");
}
console.log("before setTimeout()")
setTimeout(callback, 1000);
console.log("after setTimeout()");
控制臺輸出
before setTimeout()
after setTimeout()
(等待一秒后...)
Done
說明: setTimeout()延時函數(shù)時javascript實現(xiàn)異步執(zhí)行的手段之一,該例中它將callback放到等待隊列,并且開始為延時量計時,當?shù)竭_延時量主線程中的事件還沒執(zhí)行完成,那等待隊列中的事件繼續(xù)保持等待,直到主線程的執(zhí)行完成才被加入到線程中執(zhí)行。所以代碼中說的”等待一秒后”其實并不準確。
?可見,異步操作會在將來的某個時間點觸發(fā)一個函數(shù)調(diào)用。
?如果我們有這樣一個需求:下一個請求或函數(shù)必須要有上一步返回的數(shù)據(jù)才能執(zhí)行,如下:
request('test1.html', '', function(data1) {
console.log('第一次請求成功, 這是返回的數(shù)據(jù):', data1);
request('test2.html', data1, function (data2) {
console.log('第二次請求成功, 這是返回的數(shù)據(jù):', data2);
request('test3.html', data2, function (data3) {
console.log('第三次請求成功, 這是返回的數(shù)據(jù):', data3);
//request... 繼續(xù)請求
}, function(error3) {
console.log('第三次請求失敗, 這是失敗信息:', error3);
});
}, function(error2) {
console.log('第二次請求失敗, 這是失敗信息:', error2);
});
}, function(error1) {
console.log('第一次請求失敗, 這是失敗信息:', error1);
});
?以上出現(xiàn)了多層回調(diào)嵌套,有種暈頭轉(zhuǎn)向的感覺。這也就是我們常說的厄運回調(diào)金字塔(Pyramid of Doom),編程體驗十分不好
? 不僅如此,這種代碼結(jié)構(gòu),可讀性和可維護性太差,代碼復(fù)用率也很低,我們希望能將數(shù)據(jù)請求和數(shù)據(jù)處理區(qū)分開來,Promise就可以利用then進行「鏈式回調(diào)」,將異步操作以同步操作的流程表示出來。
sendRequest('test1.html', '').then(function(data1) {
console.log('第一次請求成功, 這是返回的數(shù)據(jù):', data1);
}).then(function(data2) {
console.log('第二次請求成功, 這是返回的數(shù)據(jù):', data2);
}).then(function(data3) {
console.log('第三次請求成功, 這是返回的數(shù)據(jù):', data3);
}).catch(function(error) {
//用catch捕捉前面的錯誤
console.log('sorry, 請求失敗了, 這是失敗信息:', error);
});
?Promise不僅能以同步的方式表示異步操作,還能catch到異常,這是ajax無法實現(xiàn)的
?
* Promise in JS
1. 基本結(jié)構(gòu)

ES6 規(guī)定,Promise對象是一個構(gòu)造函數(shù),用來生成Promise實例。
javascript代碼
var pm = new Promise(function(resolve, reject) {
// ..some code
if( /*異步操作結(jié)果*/) {
resolve(value);
} else {
reject(error);
}
})
pm.then(function(){
// handle resolveFunc
},function() {
// handle rejectFunc
})
這就是它的基本模型,為了繼續(xù)往下學習,我們需要補充一下Promise的一些基礎(chǔ)知識。
2. Promise 三種狀態(tài)
Promise鏈式調(diào)用用到resolve,reject,then,catch,他有以下三種狀態(tài)
- pending - 進行中,或者等待中,表示還沒有得到結(jié)果
- fulfilled - 已成功,在異步操作成功時調(diào)用,并將結(jié)果作為參數(shù)傳遞出去。
- rejected - 已失敗。在異步操作失敗時調(diào)用,并將報出的錯誤作為參數(shù)傳遞出去。
?只有異步操作的結(jié)果可以決定當前是哪種狀態(tài),其他任何操作都無法改變這個狀態(tài),這也是Promise名字的由來。所謂的“君子一言,駟馬難追”,承諾將來某個狀態(tài),且該狀態(tài)不會被其他因素改變。
注釋: 從基本用法的例子中我們看到Promise構(gòu)造函數(shù)的參數(shù)是
resolve和reject,并不是三種狀態(tài)中的fulfilled和rejected,原因就是:resolved表示的是已結(jié)束(已定型),它包含fullfilled和rejected兩種狀態(tài),但使用中,我們默認的將resolved當做fulfilled(成功)使用。
3. Promise 對象的特點
(1)對象的狀態(tài)不受外界因素影響。以上已經(jīng)說過。
(2)一旦結(jié)果狀態(tài)生成,就不會在改變,任何時候都可以得到這個結(jié)果。Promise對象的狀態(tài)改變,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected。只要這兩種狀態(tài)發(fā)生了,狀態(tài)就凝固不變,并一直保持這個結(jié)果。就算改變已經(jīng)發(fā)生了,你再對Promise對象添加回調(diào)函數(shù),也會立即得到這個結(jié)果。這和事件監(jiān)聽有很大的區(qū)別,事件監(jiān)聽是實時的,如果錯過了監(jiān)聽時機,就得不到要監(jiān)聽的結(jié)果了
promise也有缺點
- 無法取消Promise,一旦新建它就會立即執(zhí)行,無法中途取消
- 如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯誤,不會反應(yīng)到外部(所以健壯的代碼要catch錯誤)
- 當處于pending狀態(tài)時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)
?
* 基本API
1 .then()
語法:
Promise.prototype.then( onFulfilled, onRejected )
這是Promise最常用也是最重要的一個API,他定義了Promise的兩個回調(diào)函數(shù),并返回一個新的Promise實例,且返回值傳入這個行Promise的resolve函數(shù)中。
因此,我們可以使用鏈式寫法,如為什么要用Promise中的最后一例,返回的還是一個Promise對象(即有異步操作),這時后一個回調(diào)函數(shù),就會等待該Promise對象的狀態(tài)發(fā)生變化,才會被調(diào)用。
2. .catch() - 拋出異常
語法:
Promise.prototype.catch( onRejected )
該方法是.then(undefined, onRejected)的別名,用于指定發(fā)生錯誤時的回調(diào)函數(shù)。
var promise = new Promise(function(resolve, reject)){
// some code
}
promise.then(function(data) {
console.log('success');
}, function(error) {
conosle.log('error', error)
})
/*---等價于---*/
promise.then(function(data){
console.log('success');
}).catch(function(error) {
consol;e.log('error', error)
})
再看一例
var promise = new Promise(function(resoleve, reject) {
throw new Error('test');
});
// 等同于
var promise = new Promise(resovle, reject) {
reject(new Error('test'));
}
// 以上的reject我們用catch來捕獲
promise.catch(function (error) {
console.log(error);
});
---output---
Error: test
?從上例可以看出,reject方法的作用,等同于拋錯,這是Promise另一大優(yōu)勢
?關(guān)于Promise拋錯有幾下幾個需要注意的地方
- Promise對象的錯誤,會一直向后傳遞,知道被捕獲,即錯誤總會被下一個catch所捕獲。then方法指定的回調(diào)函數(shù),若拋出錯誤,也會被下一個catch捕獲。catch中也能拋錯,則需要后面的catch來捕獲。
sendRequest('test.html').then(function(data1) {
//do something
}).then(function (data2) {
//do something
}).catch(function (error) {
//處理前面三個Promise產(chǎn)生的錯誤
});
- promise狀態(tài)一旦改變就會凝固,不會再改變。因此promise一旦fulfilled了,再拋錯,也不會變?yōu)閞ejected,就不會被catch了。
var promise = new Promise(function(resolve, reject) {
resolve();
throw 'error';
});
promise.catch(function(e) {
console.log(e); //This is never called
});
- 如果沒有使用catch方法指定處理錯誤的回調(diào)函數(shù),Promise對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應(yīng)(Chrome會拋錯),這是Promise的另一個缺點。
var promise = new Promise(function(resolve, reject) {
resolve(); // 狀態(tài)已經(jīng)被返回為resolve
throw 'error';
});
promise.catch(function(e) {
console.log(e); //This is never called
});
拋錯實例:
var p = new Promise(function(resolve, reject) {
resolve(x);
});
p.then(function(data){
console.log(data);
});
Chrome上的表現(xiàn);

? 據(jù)說只有chrome會報錯,其他瀏覽器的錯誤不會被捕獲,也不會傳遞到外層代碼,最后沒有任何輸出,promise的狀態(tài)也變?yōu)?code>rejected
3 .all() - Promise中的“邏輯與”
? 全部執(zhí)行結(jié)束且狀態(tài)均為resolve才為resolve,有一個執(zhí)行錯誤,則為reject
語法:
promise.all( iterable )
該方法用于將多個Promise實例,包裝成一個新的Promise實例。
var p = Promise.all([p1, p2, p3]);
Promise.all方法接受一個數(shù)組(或具有Iterator接口)作參數(shù),數(shù)組中的對象(p1、p2、p3)均為promise實例(如果不是一個promise,該項會被用Promise.resolve轉(zhuǎn)換為一個promise)。它的狀態(tài)由這三個promise實例決定。
- 當p1, p2, p3狀態(tài)都變?yōu)閒ulfilled,p的狀態(tài)才會變?yōu)閒ulfilled,并將三個promise返回的結(jié)果,按參數(shù)的順序(而不是 resolved的順序)存入數(shù)組,傳給p的回調(diào)函數(shù)
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 3000, "first");
});
var p2 = new Promise(function(resolve, reject) {
resolve("second");
});
var p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 1000, "third");
});
Promise.all([p1, p2, p3]).then(function(values) {
console.log(values);
});
// ----output----
// 約3秒后
// ["first", "second", "third"]
- 當p1, p2, p3其中之一狀態(tài)變?yōu)閞ejected,p的狀態(tài)也會變?yōu)閞ejected,并把第一個被reject的promise的返回值,立即觸發(fā)并傳給p的回調(diào)函數(shù)
// 將上例中的p2適當修改如下
var p2 = new Promise(function(resolve, reject) {
resolve(x);
});
這時,p2會拋出錯誤,立即傳給Promise.all(),結(jié)束執(zhí)行。
- 這多個 promise 是同時開始、并行執(zhí)行的,而不是順序執(zhí)行
4 .race() - 競速執(zhí)行
? 需要注意的是,它并不是Promise中的“邏輯或”,而是將先結(jié)束的傳值給 then,最先執(zhí)行完成的是resolve即resolve,為reject則reject
語法:
Promise.race( iterable )
該方法同樣是將多個Promise實例,包裝成一個新的Promise實例。
var p = Promise.race([p1, p2, p3]);
-
Promise.race方法同樣接受一個數(shù)組(或具有Iterator接口)作參數(shù)。當p1, p2, p3中有一個實例的狀態(tài)發(fā)生改變(變?yōu)閒ulfilled或rejected),p的狀態(tài)就跟著改變。并把第一個改變狀態(tài)的promise的返回值,傳給p的回調(diào)函數(shù)。
執(zhí)行resolve
var p1 = new Promise(function(resolve, reject) {
setTimeout(reject, 500, "one");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "two");
});
Promise.race([p1, p2]).then(function(value) {
console.log('resolve', value);
}, function(error) {
//not called
console.log('reject', error);
});
-------output-------
resolve two
執(zhí)行reject
var p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "three");
});
var p4 = new Promise(function(resolve, reject) {
setTimeout(reject, 100, "four");
});
Promise.race([p3, p4]).then(function(value) {
//not called
console.log('resolve', value);
}, function(error) {
console.log('reject', error);
});
-------output-------
reject four
- 在第一個promise對象變?yōu)閞esolve后,并不會取消其他promise對象的執(zhí)行,如下例
var fastPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('fastPromise');
resolve('resolve fastPromise');
}, 100);
});
var slowPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('slowPromise');
resolve('resolve slowPromise');
}, 1000);
});
// 第一個promise變?yōu)閞esolve后程序停止
Promise.race([fastPromise, slowPromise]).then(function (value) {
console.log(value); // => resolve fastPromise
});
-------output-------
fastPromise
resolve fastPromise
slowPromise //仍會執(zhí)行
5 .resolve() - 立即執(zhí)行Promise-resolve
語法: 1. Promise.resolve(value); 2. Promise.resolve(promise); 3. Promise.resolve(thenable);
它可以看做new Promise()的快捷方式。
new Promise(function (resolve) {
resolve('Success');
});
//----等同于----
Promise.resolve('Success');
- 這段代碼會讓這個Promise對象立即進入
resolved狀態(tài),并將結(jié)果success傳遞給then指定的onFulfilled回調(diào)函數(shù)。由于Promise.resolve()也是返回Promise對象,因此可以用.then()處理其返回值。
Promise.resolve('success').then(function (value) {
console.log(value);
});
-------output-------
Success
//Resolving an array
Promise.resolve([1,2,3]).then(function(value) {
console.log(value[0]); // => 1
});
//Resolving a Promise
var p1 = Promise.resolve('this is p1');
var p2 = Promise.resolve(p1);
p2.then(function (value) {
console.log(value); // => this is p1
});
-
Promise.resolve()的另一個作用就是將thenable對象(即帶有then方法的對象)轉(zhuǎn)換為Promise對象。
var p1 = Promise.resolve({
then: function (resolve, reject) {
resolve("this is an thenable object!");
}
});
console.log(p1 instanceof Promise); // => true
p1.then(function(value) {
console.log(value); // => this is an thenable object!
}, function(e) {
//not called
});
- 無論是在什么時候拋異常,只要promise狀態(tài)變成resolved或rejected,狀態(tài)不會再改變,這和新建promise是一樣的。
//在回調(diào)函數(shù)前拋異常
var p1 = {
then: function(resolve) {
throw new Error("error");
resolve("Resolved");
}
};
var p2 = Promise.resolve(p1);
p2.then(function(value) {
//not called
}, function(error) {
console.log(error); // => Error: error
});
//在回調(diào)函數(shù)后拋異常
var p3 = {
then: function(resolve) {
resolve("Resolved");
throw new Error("error");
}
};
var p4 = Promise.resolve(p3);
p4.then(function(value) {
console.log(value); // => Resolved
}, function(error) {
//not called
});
6 .reject() - 立即執(zhí)行Promise-reject
語法:
Promise.reject(reason)
和上述的Promise.resolve()類似,它也是new Promise()的快捷方式。
Promise.reject(new Error('error'));
/*******等同于*******/
new Promise(function (resolve, reject) {
reject(new Error('error'));
});
這段代碼會讓這個Promise對象立即進入rejected狀態(tài),并將錯誤對象傳遞給then指定的onRejected回調(diào)函數(shù)。
?
* Promise 幾個應(yīng)用
?Promise中的.then方法,可以接收構(gòu)造函數(shù)中處理的狀態(tài)的變化,并且分別對應(yīng)執(zhí)行。.then有兩個函數(shù)參數(shù),分別接收resolved和rejected的執(zhí)行。
?簡單來說, then就是定義resolve和reject函數(shù)的,其resolve函數(shù)相當于:
function resolveFun(data) {
// data 為 promise中resolve函數(shù)中所帶的參數(shù)
}
?Promise新建后就會立即執(zhí)行。而then方法中指定的回調(diào)函數(shù),將在當前腳本所有同步任務(wù)執(zhí)行完才會執(zhí)行。如下例:
1. 執(zhí)行順序
javascript代碼
var promise = new Promise(function(resolve, reject) {
console.log('before resolved');
resolve();
console.log('after resolved');
});
promise.then(function() {
console.log('resolved');
});
console.log('outer');
-------output-------
// before resolved
// after resolved
// outer
// resolved
2.調(diào)用延時執(zhí)行函數(shù)
function timeout (ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done'); // 三個參數(shù)?往下看
})
}
timeout(100).then((value) => {
console.log(value)
})
// 輸出 done
?該例寫了一個延時調(diào)用函數(shù),設(shè)置ms時間以后,才將Promise的狀態(tài)修改為resolve,然后執(zhí)行.then中的打印操作。
?這里埋了個點,比較有意思。當給setTimeout()傳入大于兩個參數(shù)時,從第三個開始代表的是傳給延時執(zhí)行函數(shù)的參數(shù),想具體了解的可以戳這里
3. 異步加載圖片
function loadImgAsync (url) {
return new Promise(function (resolve, reject) {
var img = new Image();
image.onload = function () {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
}
image.src = url;
})
}
?上訴的例子中,先加載圖片,如果圖片加載成功,就調(diào)用resolve,否則調(diào)用reject。
4. 用Promise實現(xiàn)一個Ajax操作
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
// 封裝一個get請求的方法
function getJSON(url) {
return new Promise(function(resolve, reject) {
var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);
XHR.send();
XHR.onreadystatechange = function() {
if (XHR.readyState == 4) {
if (XHR.status == 200) {
try {
var response = JSON.parse(XHR.responseText);
resolve(response);
} catch (e) {
reject(e);
}
} else {
reject(new Error(XHR.statusText));
}
}
}
})
}
getJSON(url).then(resp => console.log(resp));
?為了健壯性,處理了很多可能出現(xiàn)的異常,總之,成功了就resolve,失敗了就reject。
?如果調(diào)用resolve函數(shù)和reject函數(shù)時帶有參數(shù),那么它們的參數(shù)會被傳遞給回調(diào)函數(shù)。reject函數(shù)的參數(shù)通常是Error對象的實例,表示拋出的錯誤;resolve函數(shù)的參數(shù)除了正常的值以外,還可能是另一個 Promise 實例
現(xiàn)在所有的庫幾乎都將ajax請求利用Promise進行了封裝,因此我們在使用jQuery等庫中的ajax請求時,都可以利用Promise來讓我們的代碼更加優(yōu)雅和簡單。這也是Promise最常用的一個場景
5. resolve參數(shù)為另一個Promise實例 - 難點
var p1 = new Promise(function(resolve, reject) {
// ..some code
})
var p2 = new Promise(function(resolve, reject){
// ..some code
resolve(p1)
})
? 上述中,p1和p2都是一個實例,定義了之后都立即執(zhí)行,但是p2的resolve方法將p1作為參數(shù),即一個異步操作的結(jié)果是返回另一個異步操作
?注意:這時,p1的狀態(tài)決定了p2的狀態(tài),如果p1的狀態(tài)是pending,那么,p2的回調(diào)函數(shù)就會等待p1狀態(tài)的改變,如果p1的狀態(tài)已經(jīng)是resolve或rejected,那么p2就會立即被執(zhí)行。
很典型的一個例子
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 => conosle.log(result))
.catch(error => console.log(error))
// 建議大家看一下瀏覽器輸出效果
// 輸出 Error: fail
分析
?上述代碼中,p1和p2被定義后都立即異步執(zhí)行,p1執(zhí)行3秒之后返回reject,p2執(zhí)行1秒后返回resolve,但p2的返回結(jié)果是p1,所以,又要等待2秒p1返回結(jié)果,即p2的返回結(jié)果是“p1的返回結(jié)果”,這樣大家就明白了,最終執(zhí)行的其實是p1的結(jié)果reject,所以p2這里調(diào)用.catch而不是.then。
?注意:調(diào)用resolve或reject并不會終結(jié)Promise的參數(shù)函數(shù)的執(zhí)行。
new Promise(function(resolve, reject) {
resolve(1);
console.log(2);
}).then(val => {
console.log(val)
})
// 2
// 1
?上面代碼中,調(diào)用resolve(1)以后,后面的console.log(2)還是會執(zhí)行,并且會首先打印出來。這是因為立即 resolved 的 Promise 是在本輪事件循環(huán)的末尾執(zhí)行,總是晚于本輪循環(huán)的同步任務(wù)。雖然有這種策略,但我們一般習慣還是將其放在最后。
6. 聊天系統(tǒng)獲取兩個用戶的信息 - Promise.all()
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同時執(zhí)行p1和p2,并在它們都完成后執(zhí)行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 獲得一個Array: ['P1', 'P2']
});
7. 多個異步任務(wù)提高容錯率 - Promise.race()
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
由于p1執(zhí)行較快,Promise的then()將獲得結(jié)果'P1'。p2仍在繼續(xù)執(zhí)行,但執(zhí)行結(jié)果將被丟棄。
* Promise常見問題
到這里,相信你已學會使用Promise了,congratulation!
1. reject 和 catch 的區(qū)別
-
promise.then(onFulfilled, onRejected)
在onFulfilled中發(fā)生異常的話,在onRejected中是捕獲不到這個異常的。 -
promise.then(onFulfilled).catch(onRejected)
.then中產(chǎn)生的異常能在.catch中捕獲
綜上所述,建議使用第二種,因為能捕獲之前的所有異常。當然了,第二種的.catch()也可以使用.then()的捕錯法表示,它們本質(zhì)上是沒有區(qū)別的,.catch === .then(null, onRejected)
2. 如果在then中拋錯,而沒有對錯誤進行處理(即catch),那么會一直保持reject狀態(tài),直到catch了錯誤
/* 例4.1 */
function taskA() {
console.log(x);
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejected(error) {
console.log("Catch Error: A or B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA) // 拋出錯誤,不繼續(xù)Task A”
.then(taskB) // .then沒有捕獲A拋出的錯,不打印 “Task B”
.catch(onRejected) // 捕獲了A的錯,打印錯誤信息
.then(finalTask); // 錯誤已經(jīng)被捕獲,執(zhí)行resolve
-------output-------
Catch Error: A or B,ReferenceError: x is not defined
Final Task
來看一下該例的流程

很明顯,A拋錯時,會按照
taskA → onRejected → finalTask這個流程來處理。A拋錯后,若沒有對它進行處理,狀態(tài)就會維持rejected,taskB不會執(zhí)行,直到catch了錯誤。
3. 每次調(diào)用then都會返回一個新創(chuàng)建的promise對象,而then內(nèi)部只是返回的數(shù)據(jù)
//方法1:對同一個promise對象同時調(diào)用 then 方法
var p1 = new Promise(function (resolve) {
resolve(100);
});
p1.then(function (value) {
return value * 2;
});
p1.then(function (value) {
return value * 2;
});
p1.then(function (value) {
console.log("finally: " + value);
});
-------output-------
finally: 100
then的調(diào)用幾乎是同時開始執(zhí)行的,且傳給每個then的value都是100,這種方法應(yīng)當避免。正確的應(yīng)該是采用鏈式調(diào)用。
//方法2:對 then 進行 promise chain 方式進行調(diào)用
var p2 = new Promise(function (resolve) {
resolve(100);
});
p2.then(function (value) {
return value * 2;
}).then(function (value) {
return value * 2;
}).then(function (value) {
console.log("finally: " + value);
});
-------output-------
finally: 400
或許上面這個案例你還沒感覺,來看看這個:
function badAsyncCall(data) {
var promise = Promise.resolve(data);
promise.then(function(value) {
//do something
return value + 1;
});
return promise;
}
badAsyncCall(10).then(function(value) {
console.log(value); //想要得到11,實際輸出10
});
-------output-------
10
正確的寫法應(yīng)該是:
function goodAsyncCall(data) {
var promise = Promise.resolve(data);
return promise.then(function(value) {
//do something
return value + 1;
});
return promise;
}
goodAsyncCall(10).then(function(value) {
console.log(value);
});
-------output-------
11
4. 在異步回調(diào)中拋錯,不會被catch到
// Errors thrown inside asynchronous functions will act like uncaught errors
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
});
promise.catch(function(e) {
console.log(e); //This is never called
});
5. promise狀態(tài)變?yōu)閞esove或reject,就凝固了,不會再改變
console.log(1);
new Promise(function (resolve, reject){
reject();
setTimeout(function (){
resolve(); //not called
}, 0);
}).then(function(){
console.log(2);
}, function(){
console.log(3);
});
console.log(4);
-------output-------
1
4
3
花了兩天工作之余的時間,總算寫完了~~看了很多資源,借鑒了許多觀點和例子,希望能幫到大家
參考資源:
?阮一峰的ES6 教程
?廖雪峰的官方網(wǎng)站
?Promise迷你書
?MDN Promise
廖雪峰的官網(wǎng)能直接在里面自定義代碼嘗試運行,挺有意思。