異步——從Callback到Promise再到Rxjs

博客地址: 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時代,這是非常難得的。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容