Promise 一種更優的非同步編程統一 方法,如果直接使用傳統的回調函數去完成複雜操作就會形成回調深淵 // 回調深淵 $.get('/url1'() => { $.get('/url2'() => { $.get('/url3'() => { $.get('/url4'() => { $.get( ...
Promise
一種更優的非同步編程統一 方法,如果直接使用傳統的回調函數去完成複雜操作就會形成回調深淵
// 回調深淵
$.get('/url1'() => {
$.get('/url2'() => {
$.get('/url3'() => {
$.get('/url4'() => {
$.get('/url5'() => {
// 大概就是這樣子的
})
})
})
})
})
CommonJS
社區提出了 Promise
規範,在ES2015
中被標準化,成為語言規範。當等待狀態改編程成功或者失敗之後就再也不能再被改變了,成功的時候觸發onFulfilled
回調,失敗的時候觸發onRejected
回調
Promise 簡單使用
new Promise
傳入一個回調函數,這個回調函數兩個參數,第一個把Promise
改成為成功的狀態,第二個參數把Promise
改變成失敗的狀態,捕獲成功和異常可以使用.then
和.catch
方法,這兩個方法返回的也是一個Promise
對象
// 演示
const promsie = new Promise((resolve, reject) => {
reject(1)
})
promsie.then((value) => {
console.log(value)
}, (err) => {
// end 執行完之後才會執行這個
console.log(err)
})
// end 會先執行
console.log('end')
不管Promise
中有沒有非同步操作,then方法中的回調函數依然會進入回調隊列中排隊,會等同步代碼執行完之後才會執行
用Promise
寫一個請求函數
function ajax (url) {
return new Promise((resove, reject) => {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
// 新方法可以直接接受一個j對象
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resove(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
ajax('/json1.json').then(ret => {
console.log(ret)
}).catch(err => {
console.log(err)
})
如果需要多個連續的請求可以使用鏈式調用
ajax('/json1.json').then(ret => {
return ajax('/json2.json')
}).then(ret => {
return ajax('/json3.json')
}).then(ret => {
return ajax('/json4.json')
})
這種鏈式調用是不是很熟悉,在jqeury
中也有鏈式調用,jquery
中是返回了本身這個對象所以可以實現鏈式調用,那麼在Promise
中是不是這樣呢
let promsie1 = ajax('/json1.json')
let promise2 = promsie1.then(ret => {
console.log(ret)
}).catch(err => {
console.log(err)
})
console.log(promsie1 === promise2) // false
let a = $("body").attr('class', 'body')
let b = a.prop('disabled', true)
console.log(a === b) // true
經過測試發現,Promise
返回的是一個全新的Promise
對象,返回全新的Promise
對象的目的就是為了實現Promise
的鏈條,每個.then
方法負責不同的任務,互不幹擾,如果不斷的鏈式調用then
方法,這裡的每個then
方法都在為上一個then
方法返回的Promise
對象去添加狀態明確後的回調,這些Promise
會依次執行,而且我們可以在then
方法中去手動返回一個Promise
回調。如果then
方法中的回調函數返回了值,則會給下一個then
方法的回調函數傳遞這個返回的值,如果沒有返回那麼預設返回的就是undefined
總結一下就是
Promise
對象的then
方法會返回一個全新的Promise
對象- 後面的
then
方法就是在為上一個then
返回的Promise
註冊回調 - 前面的
then
方法中的回調函數的返回值回作為後面then
方法回調的參數 - 如果回調中返回的是
Promise
, 那後面的then
方法的回調會等待他的結束
捕獲異常
onRejected
回調會在Promise
執行異常或者拋出的異常時觸發, 捕獲異常有兩種方式,第一種, then(成功處理的回調函數, 異常處理的回調函數)
在then
方法中傳遞兩個回調函數,第二種用.catch
方法去捕獲異常,catch
方法其實就是then
方法的別名,相當於then
方法第一個參數傳undefined
// then(成功處理的回調函數, 異常處理的回調函數)
ajax('/json1.json').then(ret => {
console.log(err)
}, err => {
console.log(err)
})
// catch
ajax('/json1.json').then(ret => {
console.log(err)
}).catch(err => {
console.log(err)
})
// catch
ajax('/json1.json').then(ret => {
console.log(err)
}).then(undefined,err => {
console.log(err)
})
這兩種方式還是有很大的差異,catch
其實是在給上一個then
返回的Promise
捕獲異常,但是如果是同一個鏈條下的Promise
的錯誤會向下傳遞直到有catch
方法捕獲,而then
方法傳遞兩個回調函數的捕獲異常的方式只會捕獲誰上一個Promise
的錯誤
ajax('/json1.json').then(ret => {
console.log(ret)
}).then(undefined, err => {
console.log(err)
}).then(ret => {
console.log(ret)
}).then(ret => {
console.log(ret)
})
// catch 捕獲異常
ajax('/json1.json').then(ret => {
console.log(ret)
}).catch(err => {
// 這裡能捕獲之前的所有Promise的異常
})
// 傳遞then 第二個參數捕獲異常
ajax('/json1.json').then(ret => {
console.log(ret)
}).then(undefined, err => {
console.log(err)
throw new Error('故意的異常')
}, (err) => {
// 這裡能捕獲故意的錯誤
}).then(ret => {
console.log(ret)
}).then(ret => {
console.log(ret)
}).catch(err => {
// 這個時候已經捕獲不到異常了,因為上一個故意的異常已經被捕獲了,根據then方法會返回一個Promise所以捕獲異常之後會返回一個成功的Promise
})
還可以全局捕獲異常, 這種全局方式捕獲異常是不推薦使用的,應該在代碼塊中明確的去捕獲對應的異常
// 瀏覽器環境中
window.addEventListener('unhandledrejection', event => {
console.log(event.reason, event.promise)
// reason 失敗原因,
// promise 失敗的Promise
event.preventDefault()
}, false)
// nodejs中
process.on('unhandledRejection', (reason, promise) => {
console.log(reason, promise)
// reason 失敗原因,
// promise 失敗的Promise
})
如果需要無論成功和錯誤都需要執行則可以用finally
來實現
ajax('/json1.json')
.then(ret => {
console.log('成功執行這個')
}).catch(err => {
console.log("失敗執行這個")
})
.finally(function() {
console.log("成功和失敗都會執行這個")
});
Promise 靜態方法
Promise.resolve
快速的一個值轉化為一個Promise
對象, 這種方式和 new Promise
返回一個值是等價的
Promise.resolve({
data: "hahah"
})
new Promise((resolve) => {
resolve({
data: "hahah"
})
})
如果傳入的是一個Promise
對象會原封不動的把這個對象返回
function ajax (url) {
return new Promise((resove, reject) => {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
// 新方法可以直接接受一個j對象
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resove(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
let promise1 = ajax('/url')
let promise2 = Promise.resolve(promise1)
console.log(promise1 === promise2) // true
如果傳入的是一個對象,並且這個對象也有一個跟Promise
一樣的then
方法,也就是說這個方也也可以接收到onFulfilled, onRejected
兩個回調,並且可以調用回調傳遞參數,這種有then
方法的對象實現了一個thenable
的介面,支持這種對象的原因是因為原生Promise
還沒有被普及之前,很多時候都是第三方的庫實現的Promise
Promise.resolve({
then (onFulfilled, onRejected) {
onFulfilled('123')
}
}).then(ret => {
console.log(ret) // 123
})
Promise.reject
快速創建一個一定是失敗的Promise
對象,這個方法的參數就是Promise
失敗的原因
Promise.reject("嘿嘿,這就是錯誤的理由").catch(err => {
console.log(err) // 嘿嘿,這就是錯誤的理由
})
Promise.all
接收一個數組,這些元素都是一個Promise
對象,這個方法會返回一個全新的Promise
對象,當內部所有Promise
的都完成之後Promise.all
返回的Promise
對象才會完成。這個時候Promise.all
返回的Promise
對象拿到的結果是一個數組,這個數組中包含了每一個Promise
返回的結果。值得註意的是只有數組中的所有Promise
都成功了結束了,Promise.all
返回的Promise
對象才會成功結束。如果數組中有一個Promise
失敗的結束了,那麼Promise.all
返回的Promise
對象也會以失敗的結束
Promise.all([
ajax('/url1'),
ajax('/url2'),
ajax('/url3'),
ajax('/url4'),
]).then(values => {
console.log(values)
}).catch(err => {
console.log(err)
})
Promise.race
與 Promise.all
方法一樣也是接收一個數組,這些元素都是一個Promise
對象,這個方法會返回一個全新的Promise
對象,但是與Promise.all
方法不同的是Promise.all
是等待所有任務的結束而結束, Promise.race
只會等待第一個結束的任務而結束
const request = ajax('/api/???')
const timeout = new Promise((resolve, reject) => {
setTimeout(() => reject('timeout'), 5000);
})
Promise.race([
request,
timeout
]).then(ret => {
console.log(ret)
}).catch(err => {
console.log(err)
})
上面代碼中,如果介面在5秒之前介面返回了,那麼我們可以正常的得到返回結果,如果5秒還沒有返回,那麼請求就沒有辦法把結果返回回來了,因為timeout
這個Promise
會在5秒後以失敗的方式結束,而Promise.race
就是以第一個結束的Promise
而結束
Promise.allSettled
與 Promise.all、Promise.race
方法一樣也是接收一個數組,這些元素都是一個Promise
對象,這個方法會返回一個全新的Promise
對象,與他們不同的是無論這些Promise
執行是成功還是失敗都是等這些Promise
都完成了之後才會完成,當有多個彼此不依賴的非同步任務成功完成時,或者總是想知道每個promise
的結果時,通常使用它
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result.status)));
// > "fulfilled"
// > "rejected"
Promise.any
與 Promise.race
方法一樣也是接收一個數組,這些元素都是一個Promise
對象,這個方法會返回一個全新的Promise
對象,不同的是只要有一個Promise
執行是成功的就算成功,只有全部都失敗了才會失敗。這個全新的Promise
的 onFulfilled
的回調函數的參數為第一個成功完成的Promise
所傳遞的數據
const alwaysError = new Promise((resolve, reject) => {
reject("失敗就失敗下一個成功");
});
const two = new Promise((resolve, reject) => {
setTimeout(resolve, 30, "我是第二個完成的Promise");
});
const three = new Promise((resolve, reject) => {
setTimeout(resolve, 70, "我是第三個個完成的Promise");
});
const one = new Promise((resolve, reject) => {
setTimeout(resolve, 10, "我是最先完成的Promise");
});
Promise.any([two, three, alwaysError, one]).then((value) => {
console.log(value); // 我是最先完成的Promise
// 這個value是最先完成的Promise傳遞的值也就是=>我是最先完成的Promise
})
Promise 執行時序問題
巨集任務,微任務
測試執行順序
console.log('global start')
Promise.resolve().then(ret => {
console.log('promise')
})
console.log('global end')
// outlog
// 1. global start
// 2. global end
// 3. promise
鏈式調用多個執行看執行順序
console.log('global start')
Promise.resolve().then(ret => {
console.log('promise1')
}).then(ret => {
console.log('promise2')
}).then(ret => {
console.log('promise3')
})
console.log('global end')
// outlog
// 1. global start
// 2. global end
// 3. promise1
// 4. promise2
// 5. promise3
加入setTimeout
console.log('global start')
setTimeout(() => {
console.log('settimeout')
}, 0);
Promise.resolve().then(ret => {
console.log('promise1')
}).then(ret => {
console.log('promise2')
}).then(ret => {
console.log('promise3')
})
console.log('global end')
// 1. global start
// 2. global end
// 3. promise1
// 4. promise2
// 5. promise3
// 6. settimeout
沒想到吧,Promise
的非同步時序執行優點特殊。舉個例子、假如我們去銀行ATM辦理存款,辦完之後突然想起要轉一筆賬,這時候肯定會直接辦理轉賬業務,不會到後面重新排隊再轉賬。這個例子中我們排隊就像在javascipt
中的等待執行的任務一樣,我們隊伍中的每一個人都對應著回調回列中的一個任務、。回調隊列中任務稱之為巨集任務
,而巨集任務執行過程中可以臨時加上一些額外需求,這些額外的需求可以選擇作為一個新的巨集任務進行到隊列中排隊。上面的setTimeout
就會作為巨集任務再次到回調隊列中排隊,也可以跟我們剛的例子一樣作為當前任務的微任務
直接在當前任務結束之後立即執行。Promise
的回調會作為微任務執行,會在本輪調用的末尾去執行,所以說上面代碼會先列印promise1,promise2,promise3
在列印settimeout
微任務
是在後來才被引入到js
中的,他的目的是為了提高整體的響應能力,目前的絕大多數非同步調用都是作為巨集任務執行。Promise、MutationObserver
和nodejs
中的process.nextTick
會作為微任務在本輪調用的末尾執行
更多內容微信公眾號搜索
充饑的泡飯
小程式搜一搜開水泡飯的博客