博客地址: http://www.liushihua.org/2017/08/31/callback-promise-rxjs.html
本文章JavaScript demo可以在博客中直接運行結果哦!
異步編程的變遷史,本文僅作為本人對異步編程知識的梳理,同時向大家展示一下各種方法的不同用法。
Callback
在遠古時代,人們?yōu)榱藢崿F(xiàn)異步編程,通常采用callback的形式,定義好一個回調(diào)函數(shù),在需要異步請求的地方,注冊回調(diào)函數(shù),等異常請求返回后,把數(shù)據(jù)傳入回調(diào)函數(shù),對異步請求的結果進行處理。
如下所示:
const callback = () => {
console.log('I am a callback');
}
console.log('Hello');
setTimeout(callback, 1000);
console.log('world);
0;
這樣的方式初看起來簡單明了,但是它經(jīng)不起復雜度的考驗,如果是多個異步的嵌套,就會立刻變得晦澀,繼續(xù)上代碼
const log = console.log;
setTimeout(() => {
log('我是第一層');
setTimeout(() => {
log('我是第二層');
setTimeout(() => {
log('我是第三層');
setTimeout(() => {
log('我是第四層');
}, 1000)
}, 1000)
}, 1000)
}, 1000);
log('hello')
0;
這樣的代碼不利于閱讀,也不利于維護,接下來出現(xiàn)的是Promise,一個很重要的類型
Promise
Promise 是代表一個承諾,它可以承諾在一定的時間內(nèi),他會完成他的事件或者拋出錯誤。
換句話說,它剛生成時,他的狀態(tài)是不確定的,在一定時間后,他的狀態(tài)肯定會確定下來,同時只有兩種情況,一種是順利完成,一種是出錯異常。在編寫代碼過程中只需要監(jiān)聽這兩個狀態(tài)就可以對其結果進行處理,通過鏈式調(diào)用的方式可以使代碼更加易讀。
下面是使用Promise的模擬耗時的異步操作
const reqPromise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Request完成');
resolve({data: 1})
},1000)
})
reqPromise.then(x => console.log(x));
0;
是不是看起來更麻煩了呢? 那么我們稍微增加一點復雜度看看。下面是Promise嵌套的情況:
const getPromise = x => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(x);
resolve({data: 1})
},1000)
});
getPromise('第一層').then(x => getPromise('第二層'))
.then(x => getPromise('第三層'))
.then(x => getPromise('第四層'))
.then(x => getPromise('第五層'))
0;
上面這段用Promise寫的比直接使用Callback寫的相比,是不是清晰了很多啊
使用扁平的鏈式調(diào)用替代多層級的嵌套結構,對于代碼維護起來也是清晰很多。
但是到此我們就滿足了嗎?
No,鏈式調(diào)用依然存在一些書寫上的問題,上面的例子全都是異步代碼,如果需要寫同步代碼的話,需要在Promise外面去寫,異步代碼是在Promise的resolve里面去寫,這樣喪失了代碼的可讀性。能不能把所有的代碼都看成是同步的呢? 這樣代碼風格不就統(tǒng)一了嗎?
最新的es2017版本中有了async/swait的寫法,它并不是一個新的功能,可以看做是Promise的語法糖。
我們把上面的Promise代碼改一下如何:
const getPromise = x => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(x);
resolve({data: 1})
},1000)
});
const run = async () => {
await getPromise('第一層').then(x => console.log('我是第一層的Resolve'))
console.log('我是第一層之后的同步代碼');
await getPromise('第二層').then(x => console.log('我是第二層的Resolve'))
console.log('我是第二層之后的同步代碼');
await getPromise('第三層').then(x => console.log('我是第三層的Resolve'))
console.log('我是第三層之后的同步代碼');
};
// 啟動
run();
0;
各位覺得這樣的代碼看起來如何呢?
我們從一開始Callback的那種一堆壓縮成了Promise的一條線。
然后又把這條線用async/await折疊成了四方的紙。
有么有感覺到巨大的進步呢?
別著急,我們可以繼續(xù)思考一下。能不能把同步和異步的代碼全部當成異步來操作呢。這樣是不是也可以去規(guī)范代碼書寫啊?
于是響應式編程應時而生。
Rxjs
Rxjs并不等于響應式編程,它只是RP(Reactive Programming)的一個類庫實現(xiàn),但是因為它在幾乎所有主流語言上都有實現(xiàn),所有rx算是rp中的中堅力量了吧。
在深入介紹rxjs之前,我們先用rxjs代碼實現(xiàn)以上的那個例子.
const { Observable: ob} = require('rxjs');
const getPromise = x => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(x);
resolve({data: 1})
},1000)
});
ob.from(getPromise('第一層'))
.map(x => console.log('我是第一層之后的異步代碼'))
.flatMap(x => getPromise('第二層'))
.map(x => console.log('我是第二層之后的異步代碼'))
.flatMap(x => getPromise('第三層'))
.map(x => console.log('我是第三層之后的異步代碼'))
.flatMap(x => getPromise('第四層'))
.map(x => console.log('我是第四層之后的異步代碼'))
.flatMap(x => getPromise('第五層'))
.map(x => console.log('我是第五層之后的異步代碼'))
.subscribe()
0;
現(xiàn)在從一疊紙又回到了一條線,不,準確的說變成了一個管道。不管是同步操作還是異步的操作,都在這個管道中被聲明,起點一個信號,經(jīng)過管道中所有的同步或異步操作之后才會到達結尾的輸出,也就是訂閱(subscribe()).
啰嗦一下
異步編程的書寫方式的變更,不僅僅是編碼效率的改變,也不僅僅是代碼風格的升級。
這是編程范式的變更。
最主要的是帶動思維方式的轉(zhuǎn)變。響應式編程可以使你從不同的角度去思考一個問題的解,在后OO時代,這是非常難得的。