前言
本文將從淺到深的去剖析promise。由於內容較多,分為上下兩篇。
內容大綱
- 非同步程式設計的解決方案(完成)
- promise是什麼(完成)
- promise的用法(完成)
- promise的優點與缺點(完成)
- promise的錯誤捕獲(待續)
- promise的原理(待續)
- promise的底層原始碼(待續)
- 自己實現一個promise(待續)
非同步程式設計的解決方案
js是一門單執行緒的語言,所以其中會涉及到很多非同步的操作,非同步程式設計的解決方案有很多種,我們主要講一種最基礎的和這篇文章的主角(promise):
回撥函式
在非同步程式設計中,這種的應用範圍最廣,舉個定時器的例子:
setTimeout(() => {
// ...
}, 1000);
複製程式碼
當然在業務中非同步請求也會用到很多,但當多個非同步操作需要序列操作的時候,就會有回撥地獄產生。
$.get(url, data1 => {
console.log(data1)
$.get(data1.url, data2 => {
console.log(data2)
$.get(data2.url, data3 => {
console.log(data3)
})
})
})
複製程式碼
程式碼沒有美感,且不利於維護。當然,我們可以通過減少程式碼巢狀,模組化等手段來修復。但是並不如下面的解決方案優雅。
佼佼者——promise
const reqMethod = (url) => {
return new Promise((reslove, reject) => {
$.get(url, data => {
if(data.success) {
reslove();
} else {
reject();
}
})
})
}
reqMethod(url).then((data1) => {
return reqMethod(data1.url);
}).then((data2) => {
return reqMethod(data2.url);
}).then((data3) => {
return reqMethod(data3.url);
})
複製程式碼
這樣的實現方式,符合易於閱讀,因為每一步操作都是按照先後順序進行的。
promise是什麼
Promise從不同角度理解,有很多種含義。
promise的一種承諾
promise從字面意思理解,就是許諾和承諾的意思,對於一種承諾而言,有三種狀態:
1、承諾還未達成,還在糾結過程中(pending狀態)
2、承諾沒有實現,失言了(rejected狀態)
3、承諾實現了,就是成功的狀態(fulfilled狀態)
複製程式碼
Promise是一種標準的規範
在這裡不過多展開,大家可以去看Promise/A+ 規範或者ECMAscript規範;
Promise是ES6提供的一種物件
Promise是一個物件,是一個建構函式,ES6將其寫進了語言標準。統一了用法。最基礎的用法如下:
const promiseMethod = new Promise((resolve, reject) => {
// some code
if(/*非同步成功的條件*/) {
resolve();
} else {
reject();
}
})
複製程式碼
Promise的用法
new Promise
Promise是一個建構函式,最基礎的作用就是用new操作符生成一個例項物件
const promiseMethod = new Promise((resolve, reject) => {
// some code
if(/*非同步成功的條件*/) {
resolve();
} else {
reject();
}
})
複製程式碼
Promise
可接受的引數是一個函式,resolve
和reject
是該函式的兩個引數,由js引擎提供,不需要自己定義。
resolve
的作用是將pending
狀態變更為fulfilled
狀態。
reject
的作用是將pending
狀態變更為rejected
狀態。
Promise新建時就會立即執行,與何時呼叫無關,與結果也無關
舉個例子
const promiseOne = new Promise((resolve, reject) => {
console.log('has finish');
resolve();
})
複製程式碼
1、console.log
在新建過程中就執行了。
2、promiseOne
的結果已經固定下來了,無論何時呼叫,結果都不會發生改變。
Promise.prototype.then
then
方法是被定義在Promise
的原型上,作用是:新增Promise
狀態改變後的回撥函式。
then
方法接收兩個引數,第一個引數是resolve
狀態執行的函式,第二個引數是reject
執行的函式。
then
方法返回的是一個新的Promise
物件。
舉個例子:
const promiseOne = new Promise((resolve, reject) => {
console.log('has finish');
resolve();
})
const promiseTwo = new Promise((resolve, reject) => {
console.log('has reject');
reject();
})
const promiseThree = promiseOne.then(() => {
console.log('成功的回撥')
}, () => {
console.log('失敗的回撥')
})
promiseTwo.then(() => {
console.log('成功的回撥')
}, () => {
console.log('失敗的回撥')
})
console.log(promiseThree);
複製程式碼
輸出的結果:
has finish
has reject
<Promise>(pending)
成功的回撥
失敗的回撥
複製程式碼
Promise.prototype.catch
catch
方法其實和then
第二個回撥函式的別名,作用是用於儲物發生時的回撥處理。
catch
捕獲兩類錯誤:
1、非同步操作時丟擲錯誤,導致狀態變為reject
2、then
回撥過程中產生的錯誤。
舉個例子:
//第一種情況:非同步操作過程中報錯
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// 第二種情況:then執行過程中報錯
const promise = new Promise((resolve, reject) => {
resolve();
})
promise.then(() => {
throw new Error('test');
}).catch(function(error) {
console.log(error);
});
複製程式碼
關於catch
和then
第二個引數的區別:
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
複製程式碼
第二種寫法的優點在於,then執行過程中的報錯,catch一樣能捕獲,優於第一種寫法。
更深入的錯誤捕獲我們單獨放一章講。
Promise.prototype.finally
finally
方法作用在於不管promise
返回的狀態是什麼,它都會在執行。
finally
不接受任何引數。
promise
.then(function(data) {
// success
})
.catch(function(err) {
// error
}).finally(() => {
});
複製程式碼
finally
中執行的狀態與promise
的結果無關,而且在方法中無法得知promise
的執行結果。
Promise.all
用法:
Promise.all([p1, p2, p3]);
複製程式碼
作用:是將多個promise
示例封裝成一個promise
例項。結果只有以下兩種情形:
- 全部成功
所有promise
都成功,總的promise
才會成功
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
const p2 = new Promise((resolve, reject) => {
resolve('hello');
})
Promise.all([p1, p2]).then(() => {
console.log('全部成功')
}).catch(() => {
console.log('全部失敗')
})
// 全部成功
複製程式碼
- 全部失敗
只要有一個promise
的狀態從pending
變成reject
就是失敗。
const p1 = new Promise((resolve, reject) => {
resolve();
})
const p2 = new Promise((resolve, reject) => {
reject();
})
Promise.all([p1, p2]).then(() => {
console.log('全部成功')
}).catch(() => {
console.log('全部失敗')
})
複製程式碼
Promise的時間如何計算?
const newDate = new Date();
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 500)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000)
})
Promise.all([p1, p2]).then(() => {
const now = new Date();
let time = now.getTime() - newDate.getTime();
console.log(time);
})
// 1001
複製程式碼
按最長的那個為準。
Promise.race
用法:
Promise.race([p1, p2, p3]);
複製程式碼
作用:是將多個promise
示例封裝成一個promise
例項。
Promise.race與Promise.all的區別
Promise.race
的狀態取決於最先完成的promise
的狀態。
舉個例子,我們需要對一個請求做5秒的timeout,就可以用Promise.race
。
const reqMethod = (url) => {
return new Promise((reslove, reject) => {
$.get(url, data => {
reslove();
})
})
}
const timeout = new Promise((resolve, reject) => {
setTimeout(() => {
reject('timeout')
}, 5000)
})
Promise.race([reqMethod(xxx),timeout]).then(() => {
console.log('請求成功')
}).catch(() => {
console.log('timeout')
})
複製程式碼
Promise.resolve
作用:需要將現有的物件轉化為promise
物件。
Promise.resolve({});
//等價於
new Promise(resolve => resolve({}));
複製程式碼
Promise.resolve會根據傳入的不同引數做不同的處理
- 傳入一個promise物件
不做任何操作,原封不動的返回該物件。
- 引數是一個thenable物件
立即執行thenable
物件中的then
方法,然後返回一個resolved
狀態的promise
。
let thenable = {
then: function(resolve, reject) {
resolve('success')
}
}
const p = Promise.resolve(thenable);
p.then((e) => {
console.log(e);
})
複製程式碼
- 不是物件
直接返回一個resolved
狀態的promise
。並將引數帶給回撥函式。
const p = Promise.resolve('引數');
p.then((e) => {
console.log(e)
})
複製程式碼
- 不傳引數
直接返回一個resolved
狀態的promise
。
Promise.reject
其作用是返回一個新的promise
例項,狀態直接為reject
Promise.reject('error');
//等價於
new Promise((resolve, reject) => reject('error'));
複製程式碼
不同於Promise.resolve
,其引數會原封不動的作為reject
的理由。
舉個例子:
const p = Promise.reject('error');
p.catch((e) => {
console.log(e)
})
// error
複製程式碼
Promise的優缺點
Promise的特點
- 狀態不受外界影響,只滿足於結果
1、promise代表一個非同步操作,一共有三種狀態:pending、fulfilled和rejected。
2、promise的結果只服從於非同步操作的結果,成功進入fulfilled狀態,失敗進入rejected狀態。
複製程式碼
- 狀態變更之後不會在改變
1、promise狀態變化只有兩種可能,一種從pending到fulfilled,或者是從pending到reject。
2、當狀態變更完時,狀態將不在發生改變。
複製程式碼
Promise的優點
- 鏈式寫法可讀性比回撥函式的寫法更優雅。
- 與回撥函式相比更加方便錯誤處理。
- 與事件體系相比,一次性返回結果,更加適用於一次性返回的結果。
Promise的缺點
- 不能取消執行過程。
- 不能讀取非同步過程中的進度。
- 生成之後物件結果不會改變。
結語
本文對promise的用法進行了詳解,之後會更新兩篇深入一點的文章:
- promise錯誤捕捉
- 聊聊promise系列(深入)