嚴(yán)肅活潑,團(tuán)結(jié)緊張。戰(zhàn)天斗地,為民為邦。
引言
實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)
在相當(dāng)長的一段時間內(nèi),對于Promise和async,我會用,但是僅僅限于實(shí)現(xiàn)一個功能,而沒有深究它的表現(xiàn),
比如我們換種寫法它會不會有不同的表現(xiàn),是不是有更好的寫法來應(yīng)對我當(dāng)前的業(yè)務(wù)場景?
所以在今天,我將回歸初心對這些方面進(jìn)行探索,介紹下職責(zé)鏈模式,因?yàn)槲腋杏X,在我現(xiàn)在遇到的場景下可以套用這種編程范式。
之后我將對我的感覺進(jìn)行驗(yàn)證。
從回調(diào)的N種寫法說起
作為一個前端,對Ajax不可謂不熟,我剛工作那會甚至有種說法,你是3k的切圖崽還是5k的切圖崽只取決于你是否了解Ajax。
不得不說通過Ajax以Json數(shù)據(jù)的形式來進(jìn)行前后端交互在真正意義上將前端分離成一個職位。
眾所周知Js是單線程異步的,在這里回調(diào)就應(yīng)用而生。
xhr的寫法我已記不太清,我們就以jQuery來舉例。
$.ajax{
url:'...',
date:'...',
type:'post',
success:function(value){
....
//這里就是回調(diào)
}
}
這是一個非常常見的場景,假如,我們要先去地址A請求,獲取到數(shù)據(jù)再去地址B請求得到數(shù)據(jù),再去地址C請求得到一個數(shù)據(jù)....
最后在執(zhí)行業(yè)務(wù)邏輯會變成什么樣呢?
$.ajax{
url:'...A',
date:'...',
type:'post',
success:function(value){
....
//這里就是回調(diào)
$.ajax{
url:'...B',
date:'...',
type:'post',
success:function(value){
....
//這里就是回調(diào)
$.ajax{
url:'...C',
date:'...',
type:'post',
success:function(value){
....
//這里就是回調(diào)
}
}
}
}
}
}
回調(diào)地獄就這么誕生了..也許你可以封裝一下,讓它好看一點(diǎn),然而這樣的代碼無論是讀起來還是改起來依舊是讓人感覺身處地獄。
幸運(yùn)的是我工作的時候jQuery已經(jīng)是2.X了,它的AJAX實(shí)現(xiàn)了Promise。所以,實(shí)際中我不必去面對這種代碼。
var promise = $.ajax{
url:'...A',
date:'...',
}
promise.then(function(value){
....
return $.ajax{
url:'...B',
date:'...'
}
});
promise.then(function(value){
...
return $.ajax{
url:'...C',
date:'...'
}
})
promise.then(function(value){
....
})
雖然看起來不怎么優(yōu)雅但是比回調(diào)地獄還是強(qiáng)太多了,不是么。
我不太清楚jQuery實(shí)現(xiàn)的promise有沒有race方法,現(xiàn)在也并無動力去驗(yàn)證。
之后出現(xiàn)了一種,generator yeild 的寫法。我并沒有應(yīng)用于實(shí)踐中過,它看起來是這個樣子的...
function* job(){
yeild 某種異步操作..
yeild 某種異步操作...
.....
}
使用yeild的話,那個異步操作會相當(dāng)于被轉(zhuǎn)換為同步的,停留在那等待異步返回才會去執(zhí)行下面的操作,個人感覺結(jié)合Promise會更好用。
雖然我沒用過,接下來就是async和await。
與其說它是新的語法,不如說是generator yeild的語法糖,它看起來通常是這樣的。
var job = async(){
await 某種異步操作..
await 某種異步操作..
...
}
幾乎和上面如出一轍,換了個寫法罷了,在實(shí)踐中我?guī)缀跏墙Y(jié)合著Promise來用。
至此,從使用上來說,已并無大礙,基本寫法就是這么簡單,但是人人都止步于此的話,誰來開發(fā)那些好用的框架呢?
所以下面讓我稍微哦深入學(xué)習(xí)一下。
Promise
從這里開始,默認(rèn)是ES6的實(shí)現(xiàn)。
簡單來說Promise就是一個可以處理異步操作的對象,提供操作這個異步操作的API,通常會保存這個異步操作的狀態(tài),同時狀態(tài)的改變不可逆。
ES6中Promise是一個構(gòu)造函數(shù),通常的用法:
var pormise = new Promise((resolve,reject)=>{
//做一些異步操作如果成功
resolve();
//如果失敗 則調(diào)用reject
})
//resolve會使這個promise對象狀態(tài)變成成功結(jié)束。
//reject則使這個promise對象狀態(tài)變成失敗結(jié)束。
rejcet 的話會返回一個新的promise對象,在這里你可以做一些彌補(bǔ)。
掌握這些,當(dāng)你需要進(jìn)行異步操作的時候,這些就已經(jīng)足夠了,但是如果你想寫出健壯的代碼還需要掌握。
var pormise = new Promise((resolve,reject)=>{
//做一些異步操作如果成功
resolve();
//如果失敗 則調(diào)用reject
})
pormise.then((value)=>{
//對這個數(shù)據(jù)作一些事情
})
promise.catch((err)=>{
//錯誤處理
})
promise.finally(()=>{
//無論狀態(tài)如何都會執(zhí)行
//文檔中說似乎無法通過參數(shù)獲得狀態(tài),我覺得,在這里寫些作為錯誤處理的兜底的代碼應(yīng)該可行。
})
到這里,如果處理得當(dāng)你的promise已經(jīng)足夠健壯,不會因?yàn)橐恍┬★L(fēng)浪而翻船。
假如你想更加優(yōu)雅的處理回調(diào)地獄,你還需要了解。
//Promise.all
const p = Promise.all([p1, p2, p3]);
//它會把多個promise包裝成一個大promise,它們是同時執(zhí)行的,只有在都結(jié)束的時候,才會調(diào)用p的then
//Pomise.race
const p = Promise.race([p1, p2, p3]);
//和上面的區(qū)別是這里是順序執(zhí)行的。
//需要注意的是,上面兩個方法都是執(zhí)行一組promise,如果有一個promise為reject狀態(tài)的話,那么它們整體,也就是說這個p也將是reject狀態(tài)。
至此無論是并發(fā),還是鏈?zhǔn)秸{(diào)用都并無大礙了,剩下的就是在實(shí)踐中去應(yīng)用了,平心而論它們看起來還是蠻簡單的。
async+await
同步與等待,這主要解決的是異步的異步問題,異步照理說是個優(yōu)點(diǎn),但是在一些場景我們往往不希望它返回的那么快,
async await便應(yīng)用而生。
//基本用法就像之前說的
async function job(){
await p1
await p2
await p3
...
}
//這里Job也會是一個Promise對象
基本可以看做generator的語法糖,用Promise.race也能做到類似的用法,問題是race不怎么好阻塞,而且明顯async的寫法要更好處理一些。
//稍微需要注意下錯誤處理
async function job(){
try{
await p1
await p2
await p3
}.catch(err){
....
}
...
}
job.then(()=>{
});
job.catch(()=>{
})
//p1,p2,p3一起做錯誤處理,之后job自身也可以錯誤處理。
//不過我更推薦分開進(jìn)行,直接使用promise的catch方法。
//需要并發(fā)的話,在內(nèi)部還能使用Promise.all
不得不說,async await結(jié)合promise能用更簡潔優(yōu)雅的代碼玩出更多的花樣,這對我之后重構(gòu)代碼將有很大的幫助。
職責(zé)鏈模式
定義:使多個對象都有機(jī)會處理請求,從而避免請求的發(fā)送者和對象之間的耦合關(guān)系。
什么意思呢?就是說將多個對象連成鏈條,然后傳一個請求進(jìn)來,總有一個能解決它。
直白來說就是使用多態(tài)來重構(gòu)的switch case。
//隨便舉個列子
//假設(shè)一個購買活動,有幾種購買可能。
//比如:預(yù)付200得到優(yōu)惠券50 預(yù)購300得到100優(yōu)惠券 預(yù)付100沒有優(yōu)惠
//通常來說一個巨大的sitch case可以解決這個問題,問題是這違反了開放-封閉原則,不利于擴(kuò)展。
var order200=(pay)=>{
if(pay===200){
console.log('預(yù)付200,得到50優(yōu)惠券')
}else{
return 'nextSuccessor' //像后傳遞。
}
}
var order300=(pay)=>{
if(pay===300){
console.log('預(yù)付300,得到100優(yōu)惠券')
}else{
return 'nextSuccessor' //像后傳遞。
}
}
var order100=(pay)=>{
if(pay===100){
console.log('預(yù)付100,無優(yōu)惠券')
}else{
return 'nextSuccessor' //像后傳遞。
}
}
var Chain =function (fn){
this.fn=fn;
this.successor=null
}
Chain.prototype.setNextSuccessor =function (successor){
return this.successor = successor;
}
Chain.prototype.passRequest = function (){
var ret = this.fn.apply(this,arguments);
if(ret === 'nextSuccessor'){
return this.successor && this.successor.passRequest.apply(this.successor,arguments);
}
return ret;
}
//包裝節(jié)點(diǎn)
var cOrder200=new Chain(order200);
var cOrder300=new Chain(order300);
var cOrder100=new Chain(order100);
//指定順序
cOrder100.setNextSuccessor(cOrder200);
cOrder200.setNextSuccessor(cOrder300);
//調(diào)用
cOrder100.passRequest(100);
cOrder100.passRequest(200);
cOrder100.passRequest(300);
這段代碼有種為了舉例而舉例,頗有殺雞用牛刀的意思,不過很好懂。
值得一提的是,在這里踩了兩個新語法糖。
因?yàn)樽饔糜虻年P(guān)系箭頭函數(shù)不能作為構(gòu)造函數(shù)來使,也不適合用來掛在prototype上。
這么看來為解決自調(diào)問題產(chǎn)生的箭頭函數(shù),設(shè)計(jì)的很精巧,它能且只能解決自調(diào)問題。
結(jié)語
emmmm..到最后我發(fā)現(xiàn)Promise和async await 雖然結(jié)合密切,但是與后面的職責(zé)鏈似乎并不關(guān)聯(lián)。
我想處理的場景也是更類似鏈?zhǔn)交卣{(diào)而不是職責(zé)鏈。
假如在職責(zé)鏈里加入一個手動的Next方法,我也能處理異步問題.....
這里似乎又可以引入命令模式。
所謂學(xué)的越多不會的越多大概就是這么一回事吧。
我現(xiàn)在除了代碼重構(gòu)的規(guī)則,又多了個命令模式想要探究。
只是這篇筆記已經(jīng)夠長了,這么看來明天學(xué)什么也有著落了。
按照這個進(jìn)度大概周末我可以開始著手重構(gòu)之前的爬蟲代碼,豈不是正好?