為什么會有同步和異步?
首先JS 是單線程的,單線程程序在執(zhí)行的時候夫凸,所有的程序都是按照順序執(zhí)行的,前面的必須處理好后面的才會執(zhí)行阱持。
JS 是單線程的夭拌,但是瀏覽器加載一些需要網(wǎng)絡(luò)請求的資源,ajax或者執(zhí)行一段 setTimeout代碼衷咽,由于是單線程鸽扁,要等這些內(nèi)容訪問或者執(zhí)行完才執(zhí)行下面的代碼,那么你發(fā)送ajax請求镶骗,執(zhí)行setTimeout 的這段時間什么也做不了桶现,這種效果對程序是一種堵塞(同步堵塞),這個時候異步就出現(xiàn)了卖词,在涉及需要等待的操作巩那,我們把代碼交給其他對應(yīng)的瀏覽器線程去執(zhí)行吏夯,在執(zhí)行結(jié)束的時候此蜈,通知我們的主線程執(zhí)行完畢,你可以操作資源了噪生,這段等待時間并不影響你程序的執(zhí)行裆赵,只是在未來的某個時間段(不確定),有一個操作一定執(zhí)行跺嗽,這就是異步(異步非阻塞)
回調(diào)
異步和同步相比战授,最難掌控的就是異步任務(wù)會什么時候完成和完成之后的回調(diào)問題。
回調(diào)是異步編程最基本的方法
像下面的例子
listen( "click", function handler(evt){
setTimeout( function request(){
ajax( "http://some.url.1", function response(text){
if (text == "hello") {
handler();
}
else if (text == "world") {
request();
}
} );
}, 500) ;
} );
console.log('doSomething')
這種地域式的回調(diào)桨嫁,令代碼的可讀性非常差V怖肌!
信任問題
在你不知道的javascript一書中璃吧,對于回調(diào)的信任問題做了闡述 當(dāng)你使用第三方的庫的方法處理回調(diào)時很有可能遇到以下信任內(nèi)容
· 調(diào)用回調(diào)的過早
· 調(diào)用回調(diào)過晚
· 調(diào)用回調(diào)次數(shù)過多會過少
· 沒有把所需要的環(huán)境/參數(shù)成功的傳給你的回調(diào)函數(shù)
· 吞掉可能出現(xiàn)的錯誤或異常
· .......
那么怎么解決這種信任問題呢楣导?
你需要一個承諾
當(dāng)你把一件事情交給別人去做的時候,這個任務(wù)可能馬上完成也可能一段時間后完成畜挨,這個人在任務(wù)完成或者失敗的時候回給你一個回應(yīng)筒繁,放心的人!巴元!回應(yīng)就表示成功了或者失敗了毡咏,沒回應(yīng)就表示正在執(zhí)行~~~
Promise(承諾) 就是這樣人
【官方】Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大逮刨。它由社區(qū)最早提出和實(shí)現(xiàn)呕缭,ES6 將其寫進(jìn)了語言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對象
三種狀態(tài):
1.pending(進(jìn)行中)
2.fulfilled (已成功)通常也稱為 resolved
3.rejected (已失敾肿堋)
特點(diǎn)
1.對象狀態(tài)不受外界影響落恼。只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài)离熏,任何其他操作都無法改變這個狀態(tài)
2.一旦改變狀態(tài)佳谦,就不會在改變,任何時候都可以得到這個結(jié)果滋戳。Promise對象的狀態(tài)改變钻蔑,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected
缺點(diǎn)
1.無法取消Promise,一旦新建它就會立即執(zhí)行奸鸯,無法中途取消咪笑。
2.如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯誤娄涩,不會反應(yīng)到外部窗怒。
3.當(dāng)處于pending狀態(tài)時,無法得知目前進(jìn)展到哪一個階段(剛剛開始還是即將完成)蓄拣。
我們看一個簡單的promise例子
Promise構(gòu)造函數(shù)接受一個函數(shù)扬虚,該函數(shù)有兩個參數(shù) resolve 和 reject,他們也是兩個函數(shù)球恤。
resolve函數(shù)的作用是:將Promise 的狀態(tài)從“未完成”變成“成功”(即從“pending”變成“resolved”)辜昵,在異步操作成功的時調(diào)用,并將一部操作的結(jié)果咽斧,作為參數(shù)傳遞出去堪置。
reject函數(shù)的作用是:將Promise 的狀態(tài)從“未完成”變成“失敗”(即從“pending”變成“rejected”),在異步操作成功的時調(diào)用张惹,并將一部操作的結(jié)果舀锨,作為參數(shù)傳遞出去。
Promise實(shí)例生成以后宛逗,可以用then方法分別指定resolved狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù)坎匿。
let promise = new Promise((resolve,reject)=>{
// 接收一個callback。參數(shù)是成功函數(shù)與失敗函數(shù)
setTimeout(()=>{
let num = parseInt(Math.random()*100);
// 如果數(shù)字大于50就調(diào)用成功的函數(shù)拧额,并且將狀態(tài)變成Resolved
if(num > 50){
resolve(num);
}else{
// 否則就調(diào)用失敗的函數(shù)碑诉,將狀態(tài)變成Rejected
reject(num)
}
},3000)
})
當(dāng)Promise執(zhí)行的內(nèi)容符合你預(yù)期的成功條件的話,就調(diào)用resolve函數(shù)侥锦,失敗就調(diào)用reject函數(shù)进栽,這兩個函數(shù)的參數(shù)會被promise捕捉到」Э眩可以在之后的回調(diào)中使用
創(chuàng)建一個承諾完成了快毛,我們?nèi)绾问褂盟?/p>
promise.then(res => {
//在構(gòu)造函數(shù)中如果你執(zhí)行力resolve函數(shù)就會到這一步
console.log(res)
}, err => {
// 執(zhí)行了reject函數(shù)會到這一步
console.log(err);
})
Promise.prototype.then()
then方法接収兩個函數(shù)格嗅,第一個是承諾成功(狀態(tài)為resolved)的回調(diào)函數(shù),第二個(可選)是承諾失斶氲邸(狀態(tài)為rejected)的回調(diào)函數(shù)屯掖。
then方法的返回值不是一個promise對象就會被包裝成一個promise對象,所以then方法支持鏈?zhǔn)秸{(diào)用襟衰。
then方法的可以幫我們串行的解決一些邏輯問題贴铜,讓我們的書寫更加順暢。
看看下面這個
ajax('first');
ajax('second');
ajax('third');
需要按順序來執(zhí)行怎么辦瀑晒?
ajax('first').success(function(res){
ajax('second').success(function(res){
ajax('third').success(function(res){
//串行完畢可以執(zhí)行你想要的內(nèi)容了
});
})
})
上面地獄式的回調(diào)绍坝,可怕!苔悦!如果使用下面的 then 鏈?zhǔn)秸{(diào)用轩褐,就會好很多
let promise = new Promise((resolve,reject)=>{
ajax('first').success(function(res){
resolve(res);
})
})
promise.then(res=>{
return new Promise((resovle,reject)=>{
ajax('second').success(function(res){
resolve(res)
})
})
}).then(res=>{
return new Promise((resovle,reject)=>{
ajax('second').success(function(res){
resolve(res)
})
})
}).then(res=>{
// 串行完畢你要做的xxx可以開始了
})
串行說完了,那并行的怎么辦玖详,當(dāng)我們有多個異步事件把介,之間并沒有先后順序,只需要全部完成就可以開始工作蟋座。
這個時候我們可以使用 Promise.all
Promise.all()
Promise.all()方法用于將多個 Promise 實(shí)例拗踢,包裝成一個新的 Promise 實(shí)例。
const p = Promise.all([p1, p2, p3]);
上面代碼中蜈七,Promise.all()方法接受一個數(shù)組作為參數(shù)秒拔,p1、p2飒硅、p3都是 Promise 實(shí)例,如果不是作谚,就會先調(diào)用下面講到的Promise.resolve方法三娩,將參數(shù)轉(zhuǎn)為 Promise 實(shí)例,再進(jìn)一步處理妹懒。另外雀监,Promise.all()方法的參數(shù)可以不是數(shù)組,但必須具有 Iterator 接口眨唬,且返回的每個成員都是 Promise 實(shí)例会前。
p的狀態(tài)由p1、p2匾竿、p3決定瓦宜,分成兩種情況。
1.只有p1岭妖、p2临庇、p3的狀態(tài)都變成fulfilled反璃,p的狀態(tài)才會變成fulfilled,此時p1假夺、p2淮蜈、p3的返回值組成一個數(shù)組,傳遞給p的回調(diào)函數(shù)已卷。
2.只要p1梧田、p2、p3之中有一個被rejected侧蘸,p的狀態(tài)就變成rejected柿扣,此時第一個被reject的實(shí)例的返回值,會傳遞給p的回調(diào)函數(shù)闺魏。
var p1 = new Promise((resolve, reject) => {
resolve('p1')
})
var p2 = new Promise((resolve, reject) => {
setTimeout(()=> {
resolve('p1')
}, 1000)
})
var p3 = new Promise((resolve, reject) => {
resolve('p3')
})
Promise.all([p1, p2, p3])
.then(val => {
console.log('then', val)
})
.catch(err => {
console.log('catch', err)
})
// then (3) ["p1", "p1", "p3"]
var p1 = new Promise((resolve, reject) => {
resolve('p1')
})
var p2 = new Promise((resolve, reject) => {
setTimeout(()=> {
resolve('p1')
}, 1000)
})
var p3 = new Promise((resolve, reject) => {
reject('p3')
})
Promise.all([p1, p2, p3])
.then(val => {
console.log('then', val)
})
.catch(err => {
console.log('catch', err)
})
// catch p3
注意: 若實(shí)例自帶了 .then() 則先執(zhí)行實(shí)例的 then 在執(zhí)行 all的then未状;若實(shí)例自帶了 .catch 則先調(diào)用 實(shí)例的 catch 再執(zhí)行 .then()。
var p1 = new Promise((resolve, reject) => {
resolve('p1')
}).then(val => {
console.log(555, val)
})
var p2 = new Promise((resolve, reject) => {
setTimeout(()=> {
resolve('p1')
}, 1000)
})
var p3 = new Promise((resolve, reject) => {
resolve('p3')
})
Promise.all([p1, p2, p3])
.then(val => {
console.log('then', val)
})
.catch(err => {
console.log('catch', err)
})
// 555 'p1'
// then [undefined, 'p1', 'p3']
Promise.race()
Promise.race()方法同樣是將多個 Promise 實(shí)例析桥,包裝成一個新的 Promise 實(shí)例司草。
const p = Promise.race([p1, p2, p3]);
上面代碼中,只要p1泡仗、p2埋虹、p3之中有一個實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變娩怎。那個率先改變的 Promise 實(shí)例的返回值搔课,就傳遞給p的回調(diào)函數(shù)。
Promise.prototype.catch()
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的別名截亦,用于指定發(fā)生錯誤時的回調(diào)函數(shù)爬泥。
var p = new Promise((resolve, reject) => {
//此處拋出異常,狀態(tài)就會變成rejected崩瓤,就會調(diào)用catch() 方法的回調(diào)函數(shù)袍啡,處理這個錯誤。
throw new Error("測試錯誤test!!!")
})
p.catch((err) => {
console.log(err) // Error: 測試錯誤test!!!
})
等同于
// 寫法一
var p = new Promise((resolve, reject) => {
throw new Error("測試錯誤test!!!")
})
p.then(null, (err) => {
console.log(err)
})
// 寫法二
var p = new Promise((resolve, reject) => {
reject(new Error("錯誤測試test!!!!"))
})
p.catch((err)=> {
console.log(err)
})
// 寫法三
var p = new Promise((resolve, reject) => {
try {
throw new Error("錯誤測試testH赐啊境输!")
} catch(err) {
reject(err)
}
})
p.catch((err)=> {
console.log(err)
})
Promise 對象的錯誤具有“冒泡”性質(zhì),會一直向后傳遞颖系,直到被捕獲為止嗅剖。也就是說,錯誤總是會被下一個catch語句捕獲嘁扼。
一般來說信粮,不要在then方法里面定義 Reject 狀態(tài)的回調(diào)函數(shù)(即then的第二個參數(shù)),總是使用catch方法比較好偷拔。
// 不建議使用
var p = new Promise((resolve, reject) => {
throw new Error("test-error")
})
p.then(val=> {
console.log(1212)
}, error => {
console.log(666, error);
})
// 建議使用
var p = new Promise((resolve, reject) => {
throw new Error("test-error")
})
p.then(val=> {
console.log(1212)
}).catch(error=> {
console.log("error", error)
})
Promise.prototype.finally()
不管Promise最后狀態(tài)如何蒋院,都會執(zhí)行 finally() 亏钩。
finally() 方法的回調(diào)函數(shù)不接受任何參數(shù)。這也就意味著finally中無法知道 Promise 的狀態(tài)欺旧。這表明姑丑,finally里面的操作應(yīng)該和狀態(tài)無關(guān),不依賴與Promise 的執(zhí)行結(jié)果辞友。
finally的本質(zhì)是 then 方法的特例栅哀。
finally 方法不一定是最后一環(huán),后面還可以在跟 .then() 称龙,返回的是一個Promise
var p = new Promise((resolve, reject)=> {
throw new Error("test~~")
})
p.then((val)=> {
console.log(11, val)
}).finally(()=> {
console.log('finally')
}).catch((err)=> {
console.log(err)
})
// finally
// Error: test~~
還有
Promise.allSettled()
Promise.allSettled()方法接受一組 Promise 實(shí)例作為參數(shù)留拾,包裝成一個新的 Promise 實(shí)例。只有等到所有這些參數(shù)實(shí)例都返回結(jié)果鲫尊,不管是fulfilled還是rejected痴柔,包裝實(shí)例才會結(jié)束。
Promise.any()
ES2021 引入了Promise.any()
方法疫向。該方法接受一組 Promise 實(shí)例作為參數(shù)咳蔚,包裝成一個新的 Promise 實(shí)例返回。只要參數(shù)實(shí)例有一個變成fulfilled
狀態(tài)搔驼,包裝實(shí)例就會變成fulfilled
狀態(tài)谈火;如果所有參數(shù)實(shí)例都變成rejected
狀態(tài),包裝實(shí)例就會變成rejected
狀態(tài)舌涨。
Promise.any()跟Promise.race()方法很像糯耍,只有一點(diǎn)不同,就是不會因?yàn)槟硞€ Promise 變成rejected狀態(tài)而結(jié)束囊嘉。
注意:
all(), race(), allSettled(), any() 都是 Promise 的方法温技;
then(), finally(), catch() 都是Promise實(shí)例的方法掛載在Promise.prototype上;