本文將從淺到深的去剖析promise。因爲內容較多,分爲上下兩篇。javascript
js是一門單線程的語言,因此其中會涉及到不少異步的操做,異步編程的解決方案有不少種,咱們主要講一種最基礎的和這篇文章的主角(promise):java
在異步編程中,這種的應用範圍最廣,舉個定時器的例子:編程
setTimeout(() => {
// ...
}, 1000);
複製代碼
固然在業務中異步請求也會用到不少,但當多個異步操做須要串行操做的時候,就會有回調地獄產生。promise
$.get(url, data1 => {
console.log(data1)
$.get(data1.url, data2 => {
console.log(data2)
$.get(data2.url, data3 => {
console.log(data3)
})
})
})
複製代碼
代碼沒有美感,且不利於維護。固然,咱們能夠經過減小代碼嵌套,模塊化等手段來修復。可是並不以下面的解決方案優雅。 bash
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從字面意思理解,就是許諾和承諾的意思,對於一種承諾而言,有三種狀態:異步編程
一、承諾還未達成,還在糾結過程當中(pending狀態)
二、承諾沒有實現,失言了(rejected狀態)
三、承諾實現了,就是成功的狀態(fulfilled狀態)
複製代碼
在這裏不過多展開,你們能夠去看Promise/A+ 規範或者ECMAscript規範;函數
Promise是一個對象,是一個構造函數,ES6將其寫進了語言標準。統一了用法。最基礎的用法以下:ui
const promiseMethod = new Promise((resolve, reject) => {
// some code
if(/*異步成功的條件*/) {
resolve();
} else {
reject();
}
})
複製代碼
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();
})
複製代碼
一、console.log
在新建過程當中就執行了。
二、promiseOne
的結果已經固定下來了,不管什麼時候調用,結果都不會發生改變。
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)
成功的回調
失敗的回調
複製代碼
catch
方法其實和then
第二個回調函數的別名,做用是用於儲物發生時的回調處理。
catch
捕獲兩類錯誤:
一、異步操做時拋出錯誤,致使狀態變爲reject
二、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同樣能捕獲,優於第一種寫法。
更深刻的錯誤捕獲咱們單獨放一章講。
finally
方法做用在於無論promise
返回的狀態是什麼,它都會在執行。
finally
不接受任何參數。
promise
.then(function(data) {
// success
})
.catch(function(err) {
// error
}).finally(() => {
});
複製代碼
finally
中執行的狀態與promise
的結果無關,並且在方法中沒法得知promise
的運行結果。
用法:
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([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
對象。
Promise.resolve({});
//等價於
new Promise(resolve => resolve({}));
複製代碼
Promise.resolve會根據傳入的不一樣參數作不一樣的處理
不作任何操做,原封不動的返回該對象。
當即執行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('error');
//等價於
new Promise((resolve, reject) => reject('error'));
複製代碼
不一樣於Promise.resolve
,其參數會原封不動的做爲reject
的理由。
舉個例子:
const p = Promise.reject('error');
p.catch((e) => {
console.log(e)
})
// error
複製代碼
一、promise表明一個異步操做,一共有三種狀態:pending、fulfilled和rejected。
二、promise的結果只服從於異步操做的結果,成功進入fulfilled狀態,失敗進入rejected狀態。
複製代碼
一、promise狀態變化只有兩種可能,一種從pending到fulfilled,或者是從pending到reject。
二、當狀態變動完時,狀態將不在發生改變。
複製代碼
本文對promise的用法進行了詳解,以後會更新兩篇深刻一點的文章: