promise
是異步的一種解決方案 相當(dāng)于優(yōu)化es5的回調(diào)函數(shù)和事件 是一個構(gòu)造函數(shù)
promise是一個對象 可以通過Object.getOwnPropertyNames() 來查看promise本身和prototype上的api方法
//["length", "name", "prototype", "all", "race", "resolve", "reject", "finally", "try"]
//["constructor", "then", "catch", "finally"]
特點:
1.對象的狀態(tài)不受外界影響 promise對象代表一個異步操作的時候會有3種狀態(tài): pedding(進(jìn)行中) fulfilled(已成功) rejected(已失敗) 只有異步操作的結(jié)果 才可以決定當(dāng)前是哪一種狀態(tài) 其他操作都不可以
2.一旦狀態(tài)發(fā)生改變,就不會再變 所以狀態(tài)的變更就只有2種可能 pedding 到fulfilled 或者pedding 到 rejected 當(dāng)狀態(tài)變更之后狀態(tài)就會凝固 不會再變 會一直保持這個結(jié)果 稱之為resolved(已定型)
promise 避免了es5在請求異步的時候 層層嵌套回調(diào)函數(shù)的坑爹行為 并且同意接口 控制異步操作更加容易
缺點:1.無法取消promise 一旦新建就會立即執(zhí)行 無法中斷 2.如果不設(shè)置回調(diào)函數(shù) promise內(nèi)部拋出的錯誤 不會反應(yīng)到外部 3.當(dāng)處于pedding的狀態(tài)時 無法得知目前進(jìn)展到哪一個階段(剛剛開始還是即將完成)
remark:如果某些事件不斷地反復(fù)發(fā)生 一般來說 使用stream模式是比部署Promise 更好的選擇
基本寫法
const promise = new Promise((resolve,reject) =>{
console.log('Promise');
resolve('resolved');
})
promise.then(value =>{
console.log(value);
},value => {
console.log('error',value)
})
//或者(建議第二種)
promise.then(value =>{
console.log(value)
}).catch(value => {
console.log('error',value)
})
console.log('Hi!')
//Promise
//Hi!
//resolved
resolve 的作用是將promise對象的狀態(tài)有pedding變?yōu)閞esolve,在異步操作成功時調(diào)用 執(zhí)行promise.prototype.then第一個函數(shù)方法
reject 的作用是將promise對象的狀態(tài)從pedding變?yōu)閞ejected 在異步操作報出錯誤時調(diào)用 帶著參數(shù) 執(zhí)行promise.prototype.then第二個函數(shù)方法 或者catch函數(shù)(建議catch函數(shù))
舉例中 Promise 新建后立即執(zhí)行,所以首先輸出是Promise 然后then方法執(zhí)行的回調(diào)函數(shù),將在當(dāng)前腳本所有同步任務(wù)執(zhí)行完才會執(zhí)行,所以resolved最后輸出
建立一個promise對象實現(xiàn)ajax請求的例子
const getJSON = function(url){
const promise = new Promise(function(resolve,reject){
const handler = function(){
if(this.readyState !== 4){
return;
}
if(this.status == 200){
resolve(this.response)
}else{
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open('GET',url);
client.onreadystatechange = hander;
client.responseType = 'json';
client.setRequestHeader('Accept','application/json');
client.send();
});
return promise;
}
getJSON('/posts.json').then(json => {
console.log('Contents:' + json);
}).catch(error => {
console.error('出錯了',error)
});
const p1 = new Promise(function(resolve,reject){
setTimeout(() => reject(new Error('fail')),3000)
console.log(1)
})
const p2 = new Promise((resolve,reject) => {
setTimeout(() => resolve(p1),1000)
})
p2.then(result => console.log(result))
.catch(error => console.log(error))
當(dāng)resolve函數(shù)的參數(shù)為另外一個promise實例的時候 p1的狀態(tài)會傳遞給p2 也就是說p1的狀態(tài)決定了p2的狀態(tài) 如果p1的狀態(tài)是pedding 那么p2的回調(diào)函數(shù)就會等待p1的狀態(tài)改變 如果p1的狀態(tài)已經(jīng)是resolved或者rejected 那么p2的回調(diào)函數(shù)將會立即執(zhí)行
上面代碼中 p1為promise 3秒變?yōu)閞ejected p2的狀態(tài)在1秒之后改變 resolve方法返回是p1 由于p2返回的是另一個Promise 導(dǎo)致p2自己的狀態(tài)無效了 由于p1的狀態(tài)決定p2的狀態(tài) 所以后面的then語句都變成針對后者(p1) 又過了2秒 p1變?yōu)閞ejected 導(dǎo)致觸發(fā)catch方法指定的回調(diào)函數(shù)
當(dāng)p2的1000毫秒 改為5000毫秒的時候 會出現(xiàn)報錯 感覺應(yīng)該是p2還并未執(zhí)行開始執(zhí)行resolve方法函數(shù)的原因 會有2秒的報錯時間 在p2開始運行resolve方法函數(shù)之后 調(diào)用p2的catch方法函數(shù)
另外注意:調(diào)用resolve和reject并不會終結(jié)Promise的參數(shù)函數(shù)的執(zhí)行 也就是說 如果Promise實例還存在同步執(zhí)行的其他代碼 依然還是會執(zhí)行 不會掉出promise實例 不過一般來說 后面執(zhí)行的代碼 應(yīng)該放在then方法里面執(zhí)行更好一點
Promise.prototype.then()
Promise實例具有then方法 也就是說 then方法是定義在prototype上的 作用是為Promise 實例添加狀態(tài)改變時的回調(diào)函數(shù)。
then第一個參數(shù)是resolve狀態(tài) 第二個為rejected狀態(tài)(建議使用catch)
then 返回的方法是一個新的Promise實例(注意不是原來那個Promise實例) 所以可以采用鏈?zhǔn)綄懛?then方法后面可以再調(diào)用另外一個then方法
Promise.prototype.catch()
處理發(fā)生錯誤時的回調(diào)函數(shù)
Promise 對象拋出的錯誤不會傳遞到外層代碼 即不會有任何反應(yīng) 瀏覽器的調(diào)試工具會報錯 但是不會影響代碼的運行
通俗的講就是 Promise會吃掉錯誤
const promise = new Promise(function (resolve, reject) {
resolve('ok');
setTimeout(function () { throw new Error('test') }, 0)
});
promise.then(function (value) { console.log(value) });
// ok
// Uncaught Error: test
上面代碼中 Promise指定在下一輪'事件循環(huán)'再拋出錯誤 到了那個時候Promise的運行已經(jīng)結(jié)束了 所以這個錯誤是在Promise函數(shù)體外拋出的 會冒泡到最外層,成了未捕獲的錯誤
一般情況下 總是建議 Promise對象后面要跟一個catch方法 來捕獲Promise內(nèi)部發(fā)生的錯誤 而且因為返回的是一個新的promise實例 所以還可以接著調(diào)用then方法
當(dāng)然 如果沒有錯誤 還是跟之前一樣 會跳過catch方法 但是如果跟在catch后面的then方法里面出現(xiàn)報錯 就與前面的catch無關(guān)了
Promise.prototype.finally()
指不管Promise對象最后狀態(tài)如何 都會執(zhí)行的操作 不依賴于Promise的執(zhí)行結(jié)果 該方法是es2018(ES9)引入標(biāo)準(zhǔn)的
finally 本質(zhì)上是then方法的特例
promise
.finally(() => {
// 語句
});
// 等同于
promise
.then(
result => {
// 語句
return result;
},
error => {
// 語句
throw error;
}
);
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
Promise.all()
const p = Promise.all([p1, p2, p3]);
// 生成一個Promise對象的數(shù)組
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
const databasePromise = connectDatabase();
const booksPromise = databasePromise
.then(findAllBooks);
const userPromise = databasePromise
.then(getCurrentUser);
Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommentations(books, user));
用于將多個Promise實例包裝成一個新的Promise實例
方法接受一個數(shù)組作為參數(shù) 如果不是就會先調(diào)用Promise.resolve方法 將參數(shù)轉(zhuǎn)化為Promise實例再進(jìn)一步處理(Promise.all方法的參數(shù)可以不是數(shù)組,但必須具有 Iterator 接口,且返回的每個成員都是 Promise 實例。)
p的狀態(tài)由p1、p2、p3決定
1 只有3個都變成fulfilled p的狀態(tài)才會變成fulfilled 此時3者的返回值組成一個數(shù)組 傳遞給p的回調(diào)函數(shù)
2 只要其中一個為rejected p的狀態(tài)就變成rejected 此時第一個被rejected的實例的返回值,會傳遞給p的回調(diào)函數(shù)
如果作為參數(shù)的Promise實例 自己定義了catch方法 那么它一旦被rejected 并不會出發(fā)Promise.all()的catch方法
因為Promise 實例中的catch方法 返回一個新的Promise實例 這個Promise實例會調(diào)用resolve()方法
如果實例本身沒有catch方法 就會調(diào)用promise.all()的catch方法
Promise.race() (比賽)
const p = Promise.race([p1, p2, p3]);
只要數(shù)組其中一個率先改變狀態(tài) p的狀態(tài)就跟著改變
其他的跟Promise.all()一致
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
//fetch 新的請求接口的api 返回一個Promise對象
Promise.resolve()
將現(xiàn)有對象轉(zhuǎn)化為Promise對象
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
Promise.resolve('foo')
// 等價于
new Promise(resolve => resolve('foo'))
- 如果參數(shù)是Promise 實例 那么Promise.resolve 將不做任何修改、原封不動的返回
2.如果是thenable對象(指具有then方法的對象)
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
Promise.resolve 方法會將這個對象轉(zhuǎn)為Promise對象,然后就立即執(zhí)行thenable對象的then方法
3.參數(shù)不具有then方法的對象或根本不是對象
原始值或者不具有then方法的對象 Promise.resolve方法返回一個新的Promise對象,狀態(tài)為resolved
4.不帶有任何參數(shù)
直接返回一個resolved狀態(tài)的Promise對象
所以如果希望得到一個Promise對象,比較方便的方法就是直接調(diào)用Promise.resovel方法
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
需要注意的是 立即resolve的Promise對象 實在本輪‘事件循環(huán)’的結(jié)束時,而不是在下一輪‘事件循環(huán)’的開始時
Promise.reject()
該方法也會返回一個新的Promise實例,該實例的狀態(tài)為rejected
const thenable = {
then(resolve, reject) {
reject('出錯了');
}
};
const p = Promise.reject(thenable);
// 等同于
const p = new Promise((resolve, reject) => reject('出錯了'))
p.catch(e => {
console.log(e === thenable);
console.log(e)
})
//true
捕獲錯誤的參數(shù)是原封不動的 為thenable對象 這一點與resolve不一致
const thenable = {
then(resolve, reject) {
resolve('出錯了');
}
};
const p = Promise.resolve(thenable);
p.then(e => {
console.log(e === thenable);
console.log(e)
})
//false
//出錯了
加載圖片
const preloadImage = function(path){
return new Promise((resolve,reject) => {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
})
}
Generator與Promise結(jié)合 返回Promise對象
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
const g = function* () {
try {
const foo = yield getFoo();
console.log(foo);
} catch (e) {
console.log(e);
}
};
function run (generator) {
const it = generator();
function go(result) {
if (result.done) return result.value;
return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}
go(it.next());
}
run(g);
Promise.try()
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
在不知道或者不想?yún)^(qū)分 函數(shù)f是同步函數(shù)還是異步操作,但是想用Promise來處理它。因為這樣就可以不管f是否包含異步操作,都用then方法執(zhí)行下一步流程,用catch方法處理f拋出的錯誤。一般就會采用上面的寫法。
但是建立空的Promise.resolve對象的執(zhí)行 因為是同步函數(shù) 那么它會在本輪事件循環(huán)的末尾執(zhí)行。
上面代碼中 函數(shù)f是同步的 但是用Promise包裝之后 變成異步執(zhí)行了
需要找到一種方法 讓同步函數(shù)同步執(zhí)行 異步函數(shù)異步執(zhí)行 并且讓他們具有統(tǒng)一的api
第一種用async()函數(shù)
async函數(shù)
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
第二種使用new Promise()
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)()
console.log('next')
//now
//next
所以有了Promise.try()的提案
Promise.try(database.users.get({id: userId}))
.then(...)
.catch(...)
事實上Promise.try就是模擬try代碼塊 就像Promise.catch模擬的是catch代碼塊