理解非同步之美:Promise與async await(一)

酸楚與甘甜發表於2018-08-13

你可能會放出一個怪物

非同步與同步相比,最難以掌控的就是非同步的任務會什麼時候完成和完成之後的回撥問題,

難以掌控的觸發狀態,讓你自己寫的程式碼當時還可以讀懂,但是過幾天、半個月之後如果不重新盤一邊邏輯,你哪知道哪個內容會先執行,借用這麼一個例子

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) ;
} );

doSomething();
複製程式碼

很難以理解這種地獄式的回撥(回撥地獄)會對可讀性有多麼大的摧毀。

首先 執行listern()

其次 doSomething()

500ms(或者更遠)後執行ajax()

ajax完成後

如果text === hello 執行handler()

如果text === world 執行request()

難受嗎???

在你不知道的javascript一書中,對於回撥的信任問題做了闡述 當你使用第三方的庫的方法處理回撥時很有可能遇到以下信任內容:

理解非同步之美:Promise與async await(一)

怎麼解決???? 這種信任問題該怎麼辦?

事實證明 你需要一個承諾

當你把一件事情交給別人去做(可能馬上就能完成的也可能是需要一段時間的)這個人在任務完成或者失敗後都會給你一個回應,這樣的人你是不是特別放心的把事情交給他,他沒回應你那麼他是正在辦事、回應你了就是成功了或者失敗了。

在javascript中這樣的人就是Promise。

Promise的例項有三個狀態,Pending(進行中)、Resolved(已完成)、Rejected(已拒絕)。當你把一件事情交給promise時,它的狀態就是Pending,任務完成了狀態就變成了Resolved、沒有完成失敗了就變成了Rejected。

言歸正傳:寫一個簡單的promise

let promise = new Promise((resolve,reject)=>{
    // 接收一個callback。引數是成功函式與失敗函式
	setTimeout(()=>{
       let num = parseInt(Math.random()*100);
       // 如果數字大於50就呼叫成功的函式,並且將狀態變成Resolved
       if(num > 50){
          resolve(num);
       }else{
        // 否則就呼叫失敗的函式,將狀態變成Rejected
          reject(num)
       }
	},10000)
})
複製程式碼

當Promise執行的內容符合你預期的成功條件的話,就呼叫resolve函式,失敗就呼叫reject函式,這兩個函式的引數會被promise捕捉到。可以在之後的回撥中使用。

建立一個承諾我們已經做完了,那麼如何使用承諾後的結果呢?

promise.then(res=>{
    console.log(res);
    //在建構函式中如果你執行力resolve函式就會到這一步
},err=>{
    // 執行了reject函式會到這一步
    console.log(err);
})
複製程式碼

then方法接收兩個函式,第一個是承諾成功(狀態為resolved)的回撥函式,一個承諾失敗(狀態為rejected)的回撥函式。

then方法的返回值不是一個promise物件就會被包裝成一個promise物件,所以then方法支援鏈式呼叫。

promise.then(res=>{ return 42}).then(res=>{console.log(res)})
// 列印出42
複製程式碼

then方法的鏈式呼叫可以幫我們序列的解決一些邏輯,當我們平時書寫有順序的非同步時間,比如

ajax('first');
ajax('second');
ajax('third');
需要按順序來執行怎麼辦?
ajax('first').success(function(res){
    ajax('second').success(function(res){
        ajax('third').success(function(res){
            //序列完畢可以執行你想要的內容了
        });
    })
})
多麼美麗而又讓人望而卻步的三角形啊!!
複製程式碼

如果使用then的鏈式呼叫呢?

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可以開始了
})
複製程式碼

而且每次執行resolve的時候,都可以把每次ajax的回撥資料進行傳遞到最後。清晰簡單明瞭。

說完序列了,那麼並行怎麼辦??? 當有多個非同步事件,之間並無聯絡而且沒有先後順序,只需要全部完成就可以開始工作了。

序列會把每一個非同步事件的等待時間進行一個相加,明顯會對完成進行一個阻塞。那麼並行的話該怎麼確定全部完成呢?

Promise.all 與 Promise.race的妙用

Promise.all 接收一個陣列,陣列的每一項都是一個promise物件。當陣列中所有的promise的狀態都達到resolved的時候,Promise.all的狀態就會變成resolved,如果有一個狀態變成了rejected,那麼Promise.all的狀態就會變成rejected(任意一個失敗就算是失敗),這就可以解決我們並行的問題。呼叫then方法時的結果成功的時候是回撥函式的引數也是一個陣列,按順序儲存著每一個promise物件resolve執行時的值。

let promise1 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(1);
	},10000)
});
let promise2 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(2);
	},9000)
});
let promise3 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(3);
	},11000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{
    console.log(res);
    //[1,2,3] 證明與哪個promise的狀態先變成resolved無關
})

let promise1 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       reject(1);
	},10000)
});
let promise2 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(2);
	},9000)
});
let promise3 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(3);
	},11000)
});
let promiseAll =Promise.all([promise1,promise2,promise3]);
promiseAll.then(res=>{
    console.log(res);
},err=>{
    console.log(err)
})
複製程式碼

理解非同步之美:Promise與async await(一)
看結果不難看出來符合之前所說的

Promise.race 競速模式 也是接受一個每一項都是promise的陣列。但是與all不同的是,第一個promise物件狀態變成resolved時自身的狀態變成了resolved,第一個promise變成rejected自身狀態就會變成rejected。第一個變成resolved的promsie的值就會被使用。

let promise1 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       reject(1);
	},10000)
});
let promise2 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(2);
	},9000)
});
let promise3 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
       resolve(3);
	},11000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{
	console.log(res);
	//列印出2 為什麼不列印出1呢?因為promise2先完成了其餘的就忽略來
},rej=>{
    console.log('rejected');
    console.log(rej)};
)
// 大家可以嘗試自己改變時間進行測試
複製程式碼

Promsie.race還有一個很重要的實際用處就是,有時候我們要去做一件事,但是超過三秒鐘左右我們就不做了那怎麼辦? 這個時候可以使用Promise.race方法

Promise.race([promise1,timeOutPromise(3000)]).then(res=>{})
// timeOutPromise延時3s左右 由於是用setTimeout來實現的並不一定準確3s(一般主執行緒在開發中不會阻塞3s以上的所以不會有太大問題)
複製程式碼

這就是我對於Promise的一些基本理解。

很難受的一件事 白天辛苦寫的1736個字的內容莫名其妙的被我刪掉了。。。。。內容都去哪了??? 很藍瘦。。。 所以晚上又重新梳理了一遍。

下一期的內容是針對於網上常見的Promise的自我實現進行一個分析,

總之一句話抓住Promise的承諾思想,就可以很好的去編寫promise的程式碼。

async 與await將會在下期或者下下期進行講解。(很抱歉,想一口氣講完的但是內容太多,我也需要慢慢梳理爭取給大家一個高質量的文章,)

我是一個應屆生,最近和朋友們維護了一個公眾號,內容是我們在從應屆生過渡到開發這一路所踩過的坑,已經我們一步步學習的記錄,如果感興趣的朋友可以關注一下,一同加油~

個人公眾號:IT面試填坑小分隊

相關文章