一、概述
1场刑、什么是 Promise般此?
MDN 對(duì) Promise 的定義:
The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
直譯:Promise對(duì)象用于異步操作,它表示一個(gè)尚未完成且預(yù)計(jì)在未來完成的異步操作牵现。
通俗的說铐懊,這個(gè)異步操作可以一起執(zhí)行多個(gè)任務(wù),函數(shù)調(diào)用后不會(huì)立即返回執(zhí)行的結(jié)果瞎疼。如果任務(wù)A需要等待科乎,可先執(zhí)行任務(wù)B,等到任務(wù)A結(jié)果返回后再繼續(xù)回調(diào)贼急。
如下茅茂,定時(shí)器的異步模式:
// code1
setTimeout(function() {
console.log('taskA, 定時(shí)器異步');
}, 0);
console.log('taskB, 同步操作');
// taskB, 同步操作
// taskA, 定時(shí)器異步
由 code1 可知,異步任務(wù)總是在當(dāng)前腳本執(zhí)行完同步任務(wù)后才執(zhí)行任務(wù)太抓。
2空闲、為什么要用 Promise?
在我們寫JavaScript 時(shí)走敌,難免是要用到回調(diào)函數(shù)的碴倾,有時(shí)候需要多層嵌套回調(diào),有時(shí)極端時(shí),會(huì)出現(xiàn)厄運(yùn)回調(diào)(如圖-厄運(yùn)回調(diào)金字塔):
一般我們?cè)陧?xiàng)目開發(fā)中用的比較多的情況是在使用 ajax 時(shí):
// code 2
request('test1.html', '', function(data1) {
console.log('第一次請(qǐng)求成功, 這是返回的數(shù)據(jù):', data1);
request('test2.html', data1, function (data2) {
console.log('第二次請(qǐng)求成功, 這是返回的數(shù)據(jù):', data2);
request('test3.html', data2, function (data3) {
console.log('第三次請(qǐng)求成功, 這是返回的數(shù)據(jù):', data3);
//request... 繼續(xù)請(qǐng)求
}, function(error3) {
console.log('第三次請(qǐng)求失敗, 這是失敗信息:', error3);
});
}, function(error2) {
console.log('第二次請(qǐng)求失敗, 這是失敗信息:', error2);
});
}, function(error1) {
console.log('第一次請(qǐng)求失敗, 這是失敗信息:', error1);
});
上述 code2 代碼出現(xiàn)了多層嵌套現(xiàn)象跌榔,這樣并不利于編碼維護(hù)和編程體驗(yàn)异雁。
這時(shí),我們就可以借用 promise 的強(qiáng)大作用矫户,用 then 鏈?zhǔn)交卣{(diào)把上述 code2 代碼簡(jiǎn)化如下 code3:
// code3
sendRequest('test1.html', '').then(function(data1) {
console.log('第一次請(qǐng)求成功, 這是返回的數(shù)據(jù):', data1);
}).then(function(data2) {
console.log('第二次請(qǐng)求成功, 這是返回的數(shù)據(jù):', data2);
}).then(function(data3) {
console.log('第三次請(qǐng)求成功, 這是返回的數(shù)據(jù):', data3);
}).catch(function(error) {
//用catch捕捉前面的錯(cuò)誤
console.log('sorry, 請(qǐng)求失敗了, 這是失敗信息:', error);
});
上面代碼code3 是不是看起來清爽啦片迅!這就是 Promise 的強(qiáng)大之處残邀。
二皆辽、基本方法
1、基本用法
Promise 有以下三種狀態(tài):
- Pending:進(jìn)行中芥挣,表示初始值
- Fulfilled:表示操作成功
- Rejected:表示操作失敗
Promise有兩種狀態(tài)改變的方式驱闷,既可以從pending轉(zhuǎn)變?yōu)閒ulfilled,也可以從pending轉(zhuǎn)變?yōu)閞ejected空免。一旦狀態(tài)改變空另,就 凝固 了,會(huì)一直保持這個(gè)狀態(tài)蹋砚,不會(huì)再發(fā)生變化扼菠。當(dāng)狀態(tài)發(fā)生變化,promise.then綁定的函數(shù)就會(huì)被調(diào)用坝咐。
2循榆、基本API
.then()
語法:Promise.prototype.then(onFulfilled, onRejected)
對(duì) promise 添加 onFulfilled 和 onRejected 回調(diào),并返回的是一個(gè)新的Promise實(shí)例(不是原來那個(gè)Promise實(shí)例)墨坚,且返回值將作為參數(shù)傳入這個(gè)新Promise的 resolve 函數(shù)秧饮。
因此,我們可以使用鏈?zhǔn)綄懛ㄔ罄海缟衔牡?code3盗尸。由于前一個(gè)回調(diào)函數(shù),返回的還是一個(gè)Promise對(duì)象(即有異步操作)帽撑,這時(shí)后一個(gè)回調(diào)函數(shù)泼各,就會(huì)等待該P(yáng)romise對(duì)象的狀態(tài)發(fā)生變化,才會(huì)被調(diào)用亏拉。
.catch()
語法:Promise.prototype.catch(onRejected)
該方法用于指定放生錯(cuò)誤時(shí)的回調(diào)函數(shù)扣蜻。
// code4
promise.then(function(data) {
console.log('success');
}).catch(function(error) {
console.log('error', error);
});
Promise 對(duì)象捕獲錯(cuò)誤,還有其它的等同寫法:
// code5
var promise = new Promise(function (resolve, reject) {
throw new Error('test');
});
/*******等同于*******/
var promise = new Promise(function (resolve, reject) {
reject(new Error('test'));
});
//用catch捕獲
promise.catch(function (error) {
console.log(error);
});
// Error: test
從 code5 中可以看出专筷,reject 方法的作用弱贼,等同于拋錯(cuò)。
Promise 對(duì)象的錯(cuò)誤磷蛹,會(huì)一直向后傳遞吮旅,直到被捕獲。即錯(cuò)誤總會(huì)被下一個(gè) catch 所捕獲。then 方法指定的回調(diào)函數(shù)庇勃,若拋出錯(cuò)誤檬嘀,也會(huì)被下一個(gè) catch 捕獲,catch 中也能拋錯(cuò)责嚷,則需要后面的 catch 來捕獲鸳兽。
// code6
sendRequest('test.html').then(function(data1) {
//do something
}).then(function (data2) {
//do something
}).catch(function (error) {
//處理前面三個(gè)Promise產(chǎn)生的錯(cuò)誤
});
Promise 狀態(tài)一旦改變就會(huì)凝固,不會(huì)在改變罕拂。一次 Promise 一旦 fulfilled 了揍异,再拋錯(cuò),也不會(huì)變?yōu)?rejected爆班,就不會(huì)被 catch 了衷掷。
// code7
var promise = new Promise(function(resolve, reject) {
resolve();
throw 'error';
});
promise.catch(function(e) {
console.log(e); //This is never called
});
//在回調(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
});
如果沒有使用 catch 方法指定處理錯(cuò)誤的回調(diào)函數(shù),Promise 對(duì)象拋出的錯(cuò)誤不會(huì)傳遞到外層代碼柿菩,即不會(huì)有任何反應(yīng)(chrome 會(huì)拋錯(cuò)戚嗅,Safari 和 Firefox 不拋錯(cuò)),這是 Promise 的一個(gè)缺點(diǎn)枢舶。
// code8
var promise = new Promise(function (resolve, reject) {
resolve(x);
});
promise.then(function (data) {
console.log(data);
});
// Uncaught (in promise) ReferenceError: x is not defined
.all()
語法:Promise.all(iterable)
該方法把多個(gè) Promise 實(shí)例懦胞,打包成一個(gè)新的 Promise 實(shí)例。
// code9
var p = Promiese.all([p1, p2, p3]);
Promise.all 方法接受一個(gè)數(shù)組(或具有 Iterator 接口的對(duì)象)作為參數(shù)凉泄,數(shù)組中的對(duì)象([p1, p2, p3)均為 Promise 實(shí)例(如果不是一個(gè) Promise躏尉,該項(xiàng)會(huì)被用 Promise.resolve 放轉(zhuǎn)換為一個(gè) Promise),它的狀態(tài)由這個(gè)上 Promise 實(shí)例決定旧困。
- 當(dāng)三個(gè)實(shí)例的狀態(tài)都變味 fulfilled醇份,p的狀態(tài)菜戶變?yōu)?fulfilled,并將三個(gè) Promise 返回的結(jié)果吼具,按照參數(shù)的順序(而不是 resolved 的順序)存入數(shù)組僚纷,傳給 p 的回調(diào)函數(shù),如 code10拗盒。
- 當(dāng)三個(gè)實(shí)例中有一個(gè)裝填為 rejected怖竭,p 的狀態(tài)也會(huì)變?yōu)?rejected,并把第一個(gè) reject 的 Promise 的返回值陡蝇,傳給 p 的回調(diào)函數(shù)痊臭,如 code11.
// code10
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 3000, "first");
});
var p2 = new Promise(function (resolve, reject) {
resolve('second');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "third");
});
Promise.all([p1, p2, p3]).then(function(values) {
console.log(values);
});
// 約 3s 后
["first", "second", "third"]
// code11
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "one");
});
var p2 = new Promise((resolve, reject) => {
setTimeout(reject, 2000, "two");
});
var p3 = new Promise((resolve, reject) => {
reject("three");
});
Promise.all([p1, p2, p3]).then(function (value) {
console.log('resolve', value);
}, function (error) {
console.log('reject', error); // => reject three
});
// reject three
這多個(gè) promise 是同時(shí)開始倚搬、并行執(zhí)行的岛抄,而不是順序執(zhí)行。從下面 code12 例子可以看出董栽。如果一個(gè)個(gè)執(zhí)行恼策,那至少需要 1+32+64+128鸦致。
// code12
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
var startDate = Date.now();
Promise.all([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (values) {
console.log(Date.now() - startDate + 'ms');
console.log(values);
});
// 130ms //不一定,但大于128ms
[1,32,64,128]
.race()
語法: Promise.race(iterable)
該方法也是將多個(gè) Promise 實(shí)例,打包成一個(gè)新的 Promise 實(shí)例分唾。
var p = Promise.race([p1, p2, p3]);
Promise.race方法同樣接受一個(gè)數(shù)組(或具有Iterator接口)作參數(shù)抗碰。當(dāng)p1, p2, p3中任一個(gè)實(shí)例的狀態(tài)發(fā)生改變(變?yōu)閒ulfilled或rejected),p的狀態(tài)就跟著改變绽乔。并把第一個(gè)改變狀態(tài)的 promise 的返回值弧蝇,傳給 p 的回調(diào)函數(shù)。
// code13
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);
});
// resolve two
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);
});
// reject four
.resolve()
語法:
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);
該方法可以看做 new Promise() 的快捷方式折砸。
// code14
Promise.resolve('Success');
/*******等同于*******/
new Promise(function (resolve) {
resolve('Success');
});
這段代碼會(huì)讓這個(gè)Promise對(duì)象立即進(jìn)入resolved狀態(tài)看疗,并將結(jié)果success傳遞給then指定的onFulfilled回調(diào)函數(shù)。由于Promise.resolve()也是返回Promise對(duì)象鞍爱,因此可以用.then()處理其返回值鹃觉。
// code15
Promise.resolve('success').then(function (value) {
console.log(value);
});
// Success
// code16
//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()的另一個(gè)作用就是將thenable對(duì)象(即帶有then方法的對(duì)象)轉(zhuǎn)換為promise對(duì)象专酗。
// code17
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
});
.reject()
語法: Promise.reject(reason)
這個(gè)方法和上述的Promise.resolve()類似睹逃,它也是new Promise()的快捷方式。
// code 18
Promise.reject(new Error('error'));
/*******等同于*******/
new Promise(function (resolve, reject) {
reject(new Error('error'));
});
3祷肯、兩個(gè)附加方法
.done()
語法:
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
Promise對(duì)象的回調(diào)鏈沉填,不管以then方法或catch方法結(jié)尾,要是最后一個(gè)方法拋出錯(cuò)誤佑笋,都有可能無法捕捉到(因?yàn)镻romise內(nèi)部的錯(cuò)誤不會(huì)冒泡到全局)翼闹。因此,我們可以提供一個(gè)done方法蒋纬,總是處于回調(diào)鏈的尾端猎荠,保證拋出任何可能出現(xiàn)的錯(cuò)誤。
.finally()
finally方法用于指定不管Promise對(duì)象最后狀態(tài)如何蜀备,都會(huì)執(zhí)行的操作关摇。它與done方法的最大區(qū)別,它接受一個(gè)普通的回調(diào)函數(shù)作為參數(shù)碾阁,該函數(shù)不管怎樣都必須執(zhí)行输虱。
// code19
Promise.resolve().then(()=>{
if(needToContinueProcess) return xxx;
return Promise.reject({final})
})
.then(processOne)
.then(processTwo)
.catch(err=>{
if(err instanceof Error) return handleError
})
.finally()`
三、使用注意事項(xiàng)
缺點(diǎn):
1脂凶、無法取消 Promise宪睹。一旦新建它就會(huì)立即執(zhí)行,無法中途取消蚕钦。
2亭病、如果不設(shè)置回調(diào)函數(shù),Promise 內(nèi)部拋出的錯(cuò)誤不會(huì)反應(yīng)到外部嘶居。
3罪帖、當(dāng)處于 pending 狀態(tài)時(shí),無法得知目前進(jìn)展到哪一階段。
注意事項(xiàng):
1胸蛛、始終在Promise 構(gòu)造器中書寫邏輯的話污茵,即使出現(xiàn)了意外的輸入,也能絕大部分情況下返回一個(gè)Rejected 的 Promise
2葬项、在 Promise 構(gòu)造器中泞当,除非你明確知道使用 throw 的正確姿勢(shì),否則都請(qǐng)使用 reject民珍。
3襟士、能夠兼容 Promise 和 Callback 確實(shí)是件很棒的事情,用第三方代碼庫(如bluebird)前請(qǐng)盡量理解其原理嚷量,短小的話完全可以自己寫一個(gè)陋桂。Promise雖好,可不要亂用哦蝶溶,實(shí)時(shí)牢記它會(huì)吞沒錯(cuò)誤的風(fēng)險(xiǎn)嗜历。
四、自我檢驗(yàn)題
https://zhuanlan.zhihu.com/p/30797777
五抖所、結(jié)語
本章的重點(diǎn)是需要明白什么是Promise梨州?為什么要使用 Promise?在使用 Promise 知道基本用法和注意事項(xiàng)田轧。下一章我們來認(rèn)識(shí)一下 ES6 中的遍歷器 Iterator暴匠。
六傻粘、參考
- https://segmentfault.com/a/1190000007032448#articleHeader6
- 阮一峰《ES5標(biāo)準(zhǔn)入門(第三版)》
章節(jié)目錄
1每窖、ES6中啥是塊級(jí)作用域弦悉?運(yùn)用在哪些地方?
2警绩、ES6中使用解構(gòu)賦值能帶給我們什么崇败?
3、ES6字符串?dāng)U展增加了哪些后室?
4混狠、ES6對(duì)正則做了哪些擴(kuò)展岸霹?
5、ES6數(shù)值多了哪些擴(kuò)展将饺?
6贡避、ES6函數(shù)擴(kuò)展(箭頭函數(shù))
7、ES6 數(shù)組給我們帶來哪些操作便利刮吧?
8、ES6 對(duì)象擴(kuò)展
9井厌、Symbol 數(shù)據(jù)類型在 ES6 中起什么作用致讥?
10仅仆、Map 和 Set 兩數(shù)據(jù)結(jié)構(gòu)在ES6的作用
11垢袱、ES6 中的Proxy 和 Reflect 到底是什么鬼?
12咳榜、從 Promise 開始踏入異步操作之旅
13姚糊、ES6 迭代器(Iterator)和 for...of循環(huán)使用方法
14、ES6 異步進(jìn)階第二步:Generator 函數(shù)
15救恨、JavaScript 異步操作進(jìn)階第三步:async 函數(shù)
16释树、ES6 構(gòu)造函數(shù)語法糖:class 類