Promise 必須爲如下三種狀態之一:等待態(Pending)、執行態(Fulfilled)和拒絕態(Rejected)。一旦Promise 被 resolve 或 reject,不能再遷移至其餘任何狀態(即狀態 immutable)。前端
基本過程:git
真正的鏈式Promise是指在當前promise達到fulfilled狀態後,即開始進行下一個promise.es6
先從 Promise 執行結果看一下,有以下一段代碼:github
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
resolve({ test: 2 })
reject({ test: 2 })
}, 1000)
}).then((data) => {
console.log('result1', data)
},(data1)=>{
console.log('result2',data1)
}).then((data) => {
console.log('result3', data)
})
//result1 { test: 1 }
//result3 undefined
複製代碼
顯然這裏輸出了不一樣的 data。由此能夠看出幾點:數組
基於以上幾點,咱們先寫個基於 PromiseA+ 規範的只含 resolve 方法的 Promise 模型:promise
function Promise(fn){
let state = 'pending';
let value = null;
const callbacks = [];
this.then = function (onFulfilled){
return new Promise((resolve, reject)=>{
handle({ //橋樑,將新 Promise 的 resolve 方法,放到前一個 promise 的回調對象中
onFulfilled,
resolve
})
})
}
function handle(callback){
if(state === 'pending'){
callbacks.push(callback)
return;
}
if(state === 'fulfilled'){
if(!callback.onFulfilled){
callback.resolve(value)
return;
}
const ret = callback.onFulfilled(value) //處理回調
callback.resolve(ret) //處理下一個 promise 的resolve
}
}
function resolve(newValue){
const fn = ()=>{
if(state !== 'pending')return
state = 'fulfilled';
value = newValue
handelCb()
}
setTimeout(fn,0) //基於 PromiseA+ 規範
}
function handelCb(){
while(callbacks.length) {
const fulfiledFn = callbacks.shift();
handle(fulfiledFn);
};
}
fn(resolve)
}
複製代碼
這個模型簡單易懂,這裏最關鍵的點就是在 then 中新建立的 Promise,它的狀態變爲 fulfilled 的節點是在上一個 Promise的回調執行完畢的時候。也就是說當一個 Promise 的狀態被 fulfilled 以後,會執行其回調函數,而回調函數返回的結果會被看成 value,返回給下一個 Promise(也就是then 中產生的 Promise),同時下一個 Promise的狀態也會被改變(執行 resolve 或 reject),而後再去執行其回調,以此類推下去...鏈式調用的效應就出來了。瀏覽器
可是若是僅僅是例子中的狀況,咱們能夠這樣寫:異步
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
console.log('result3')
})
//result1 { test: 1 }
//result3
複製代碼
實際上,咱們經常使用的鏈式調用,是用在異步回調中,以解決"回調地獄"的問題。以下例子:函數
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
return test()
}).then((data) => {
console.log('result2', data)
})
function test(id) {
return new Promise(((resolve) => {
setTimeout(() => {
resolve({ test: 2 })
}, 5000)
}))
}
//基於第一個 Promise 模型,執行後的輸出
//result1 { test: 1 }
//result2 Promise {then: ƒ}
複製代碼
用上面的 Promise 模型,獲得的結果顯然不是咱們想要的。認真看上面的模型,執行 callback.resolve 時,傳入的參數是 callback.onFulfilled 執行完成的返回,顯然這個測試例子返回的就是一個 Promise,而咱們的 Promise 模型中的 resolve 方法並無特殊處理。那麼咱們將 resolve 改一下:學習
function Promise(fn){
...
function resolve(newValue){
const fn = ()=>{
if(state !== 'pending')return
if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
const {then} = newValue
if(typeof then === 'function'){
// newValue 爲新產生的 Promise,此時resolve爲上個 promise 的resolve
//至關於調用了新產生 Promise 的then方法,注入了上個 promise 的resolve 爲其回調
then.call(newValue,resolve)
return
}
}
state = 'fulfilled';
value = newValue
handelCb()
}
setTimeout(fn,0)
}
...
}
複製代碼
用這個模型,再測試咱們的例子,就獲得了正確的結果:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
return test()
}).then((data) => {
console.log('result2', data)
})
function test(id) {
return new Promise(((resolve, reject) => {
setTimeout(() => {
resolve({ test: 2 })
}, 5000)
}))
}
//result1 { test: 1 }
//result2 { test: 2 }
複製代碼
顯然,新增的邏輯就是針對 resolve 入參爲 Promise 的時候的處理。咱們觀察一下 test 裏面建立的 Promise,它是沒有調用 then方法的。從上面的分析咱們已經知道 Promise 的回調函數就是經過調用其 then 方法註冊的,所以 test 裏面建立的 Promise 其回調函數爲空。
顯然若是沒有回調函數,執行 resolve 的時候,是沒辦法鏈式下去的。所以,咱們須要主動爲其注入回調函數。
咱們只要把第一個 then 中產生的 Promise 的 resolve 函數的執行,延遲到 test 裏面的 Promise 的狀態爲 onFulfilled 的時候再執行,那麼鏈式就能夠繼續了。因此,當 resolve 入參爲 Promise 的時候,調用其 then 方法爲其注入回調函數,而注入的是前一個 Promise 的 resolve 方法,因此要用 call 來綁定 this 的指向。
基於新的 Promise 模型,上面的執行過程產生的 Promise 實例及其回調函數,能夠用看下錶:
Promise | callback |
---|---|
P1 | [{onFulfilled:c1(第一個then中的fn),resolve:p2resolve}] |
P2 (P1 調用 then 時產生) | [{onFulfilled:c2(第二個then中的fn),resolve:p3resolve}] |
P3 (P2 調用 then 時產生) | [] |
P4 (執行c1中產生[調用 test ]) | [{onFulfilled:p2resolve,resolve:p5resolve}] |
P5 (調用p2resolve 時,進入 then.call 邏輯中產生) | [] |
有了這個表格,咱們就能夠清晰知道各個實例中 callback 執行的順序是:
c1 -> p2resolve -> c2 -> p3resolve -> [] -> p5resolve -> []
以上就是鏈式調用的原理了。
下面咱們再來補全 reject 的邏輯。只須要在註冊回調、狀態改變時加上 reject 的邏輯便可。
完整代碼以下:
function Promise(fn){
let state = 'pending';
let value = null;
const callbacks = [];
this.then = function (onFulfilled,onRejected){
return new Promise((resolve, reject)=>{
handle({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
function handle(callback){
if(state === 'pending'){
callbacks.push(callback)
return;
}
const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
const next = state === 'fulfilled'? callback.resolve:callback.reject;
if(!cb){
next(value)
return;
}
const ret = cb(value)
next(ret)
}
function resolve(newValue){
const fn = ()=>{
if(state !== 'pending')return
if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
const {then} = newValue
if(typeof then === 'function'){
// newValue 爲新產生的 Promise,此時resolve爲上個 promise 的resolve
//至關於調用了新產生 Promise 的then方法,注入了上個 promise 的resolve 爲其回調
then.call(newValue,resolve, reject)
return
}
}
state = 'fulfilled';
value = newValue
handelCb()
}
setTimeout(fn,0)
}
function reject(error){
const fn = ()=>{
if(state !== 'pending')return
if(error && (typeof error === 'object' || typeof error === 'function')){
const {then} = error
if(typeof then === 'function'){
then.call(error,resolve, reject)
return
}
}
state = 'rejected';
value = error
handelCb()
}
setTimeout(fn,0)
}
function handelCb(){
while(callbacks.length) {
const fn = callbacks.shift();
handle(fn);
};
}
fn(resolve, reject)
}
複製代碼
異常一般是指在執行成功/失敗回調時代碼出錯產生的錯誤,對於這類異常,咱們使用 try-catch 來捕獲錯誤,並將 Promise 設爲 rejected 狀態便可。
handle代碼改造以下:
function handle(callback){
if(state === 'pending'){
callbacks.push(callback)
return;
}
const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
const next = state === 'fulfilled'? callback.resolve:callback.reject;
if(!cb){
next(value)
return;
}
try {
const ret = cb(value)
next(ret)
} catch (e) {
callback.reject(e);
}
}
複製代碼
咱們實際使用時,常習慣註冊 catch 方法來處理錯誤,例:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
return test()
}).catch((ex) => {
console.log('error', ex)
})
複製代碼
實際上,錯誤也好,異常也罷,最終都是經過reject實現的。也就是說能夠經過 then 中的錯誤回調來處理。因此咱們能夠增長這樣的一個 catch 方法:
function Promise(fn){
...
this.then = function (onFulfilled,onRejected){
return new Promise((resolve, reject)=>{
handle({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
this.catch = function (onError){
this.then(null,onError)
}
...
}
複製代碼
在實際應用的時候,咱們很容易會碰到這樣的場景,無論Promise最後的狀態如何,都要執行一些最後的操做。咱們把這些操做放到 finally 中,也就是說 finally 註冊的函數是與 Promise 的狀態無關的,不依賴 Promise 的執行結果。因此咱們能夠這樣寫 finally 的邏輯:
function Promise(fn){
...
this.catch = function (onError){
this.then(null,onError)
}
this.finally = function (onDone){
this.then(onDone,onDone)
}
...
}
複製代碼
實際應用中,咱們可使用 Promise.resolve 和 Promise.reject 方法,用於將於將非 Promise 實例包裝爲 Promise 實例。以下例子:
Promise.resolve({name:'winty'})
Promise.reject({name:'winty'})
// 等價於
new Promise(resolve => resolve({name:'winty'}))
new Promise((resolve,reject) => reject({name:'winty'}))
複製代碼
這些狀況下,Promise.resolve 的入參可能有如下幾種狀況:
基於以上幾點,咱們能夠實現一個 Promise.resolve 方法以下:
function Promise(fn){
...
this.resolve = function (value){
if (value && value instanceof Promise) {
return value;
} else if (value && typeof value === 'object' && typeof value.then === 'function'){
let then = value.then;
return new Promise(resolve => {
then(resolve);
});
} else if (value) {
return new Promise(resolve => resolve(value));
} else {
return new Promise(resolve => resolve());
}
}
...
}
複製代碼
Promise.reject與Promise.resolve相似,區別在於Promise.reject始終返回一個狀態的rejected的Promise實例,而Promise.resolve的參數若是是一個Promise實例的話,返回的是參數對應的Promise實例,因此狀態不一 定。 所以,reject 的實現就簡單多了,以下:
function Promise(fn){
...
this.reject = function (value){
return new Promise(function(resolve, reject) {
reject(value);
});
}
...
}
複製代碼
入參是一個 Promise 的實例數組,而後註冊一個 then 方法,而後是數組中的 Promise 實例的狀態都轉爲 fulfilled 以後則執行 then 方法。這裏主要就是一個計數邏輯,每當一個 Promise 的狀態變爲 fulfilled 以後就保存該實例返回的數據,而後將計數減一,當計數器變爲 0 時,表明數組中全部 Promise 實例都執行完畢。
function Promise(fn){
...
this.all = function (arr){
var args = Array.prototype.slice.call(arr);
return new Promise(function(resolve, reject) {
if(args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
try {
if(val && (typeof val === 'object' || typeof val === 'function')) {
var then = val.then;
if(typeof then === 'function') {
then.call(val, function(val) {
res(i, val);
}, reject);
return;
}
}
args[i] = val;
if(--remaining === 0) {
resolve(args);
}
} catch(ex) {
reject(ex);
}
}
for(var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
}
...
}
複製代碼
有了 Promise.all 的理解,Promise.race 理解起來就更容易了。它的入參也是一個 Promise 實例數組,而後其 then 註冊的回調方法是數組中的某一個 Promise 的狀態變爲 fulfilled 的時候就執行。由於 Promise 的狀態只能改變一次,那麼咱們只須要把 Promise.race 中產生的 Promise 對象的 resolve 方法,注入到數組中的每個 Promise 實例中的回調函數中便可。
function Promise(fn){
...
this.race = function(values) {
return new Promise(function(resolve, reject) {
for(var i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject);
}
});
}
...
}
複製代碼
Promise 源碼不過幾百行,咱們能夠從執行結果出發,分析每一步的執行過程,而後思考其做用便可。其中最關鍵的點就是要理解 then 函數是負責註冊回調的,真正的執行是在 Promise 的狀態被改變以後。而當 resolve 的入參是一個 Promise 時,要想鏈式調用起來,就必須調用其 then 方法(then.call),將上一個 Promise 的 resolve 方法注入其回調數組中。
雖然 then 廣泛認爲是微任務。可是瀏覽器沒辦法模擬微任務,目前要麼用 setImmediate ,這個也是宏任務,且不兼容的狀況下仍是用 setTimeout 打底的。還有,promise 的 polyfill (es6-promise) 裏用的也是 setTimeout。所以這裏就直接用 setTimeout,以宏任務來代替微任務了。
function Promise(fn) {
let state = 'pending'
let value = null
const callbacks = []
this.then = function (onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
handle({
onFulfilled,
onRejected,
resolve,
reject,
})
})
}
this.catch = function (onError) {
this.then(null, onError)
}
this.finally = function (onDone) {
this.then(onDone, onError)
}
this.resolve = function (value) {
if (value && value instanceof Promise) {
return value
} if (value && typeof value === 'object' && typeof value.then === 'function') {
const { then } = value
return new Promise((resolve) => {
then(resolve)
})
} if (value) {
return new Promise(resolve => resolve(value))
}
return new Promise(resolve => resolve())
}
this.reject = function (value) {
return new Promise(((resolve, reject) => {
reject(value)
}))
}
this.all = function (arr) {
const args = Array.prototype.slice.call(arr)
return new Promise(((resolve, reject) => {
if (args.length === 0) return resolve([])
let remaining = args.length
function res(i, val) {
try {
if (val && (typeof val === 'object' || typeof val === 'function')) {
const { then } = val
if (typeof then === 'function') {
then.call(val, (val) => {
res(i, val)
}, reject)
return
}
}
args[i] = val
if (--remaining === 0) {
resolve(args)
}
} catch (ex) {
reject(ex)
}
}
for (let i = 0; i < args.length; i++) {
res(i, args[i])
}
}))
}
this.race = function (values) {
return new Promise(((resolve, reject) => {
for (let i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject)
}
}))
}
function handle(callback) {
if (state === 'pending') {
callbacks.push(callback)
return
}
const cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected
const next = state === 'fulfilled' ? callback.resolve : callback.reject
if (!cb) {
next(value)
return
}
try {
const ret = cb(value)
next(ret)
} catch (e) {
callback.reject(e)
}
}
function resolve(newValue) {
const fn = () => {
if (state !== 'pending') return
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
const { then } = newValue
if (typeof then === 'function') {
// newValue 爲新產生的 Promise,此時resolve爲上個 promise 的resolve
// 至關於調用了新產生 Promise 的then方法,注入了上個 promise 的resolve 爲其回調
then.call(newValue, resolve, reject)
return
}
}
state = 'fulfilled'
value = newValue
handelCb()
}
setTimeout(fn, 0)
}
function reject(error) {
const fn = () => {
if (state !== 'pending') return
if (error && (typeof error === 'object' || typeof error === 'function')) {
const { then } = error
if (typeof then === 'function') {
then.call(error, resolve, reject)
return
}
}
state = 'rejected'
value = error
handelCb()
}
setTimeout(fn, 0)
}
function handelCb() {
while (callbacks.length) {
const fn = callbacks.shift()
handle(fn)
}
}
fn(resolve, reject)
}
複製代碼
以爲內容有幫助能夠關注下個人公衆號 「前端Q」,一塊兒學習成長~~