JavaScript 中回調地獄的此生前世

1. 講個笑話前端

JavaScript 是一門編程語言git

2. 異步編程github

JavaScript 因爲某種緣由是被設計爲單線程的,同時因爲 JavaScript 在設計之初是用於瀏覽器的 GUI 編程,這也就須要線程不能進行阻塞。sql

因此在後續的發展過程當中基本都採用異步非阻塞的編程模式。編程

簡單來講,異步編程就是在執行一個指令以後不是立刻獲得結果,而是繼續執行後面的指令,等到特定的事件觸發後,才獲得結果。promise

也正是由於這樣,咱們經常會說: JavaScript 是由事件驅動的。瀏覽器

3. 異步實現babel

用 JavaScript 構建一個應用的時候常常會遇到異步編程,不論是 Node 服務端仍是 Web 前端。異步

那如何去進行異步編程呢?就目前的標準以及草案來看,主要有下面的幾種方式:async

  • 回調

  • promise

  • Generator

  • await/async

3.1 回調

這種異步的方式是最基礎的實現,若是你曾經寫過一點的 Node, 可能常常會遇到這樣的代碼:

connection.query(sql, (err, result) => {

   if(err) {

       console.err(err)

   } else {

       connection.query(sql, (err, result) => {

           if(err) {

               console.err(err)

           } else {

               ...

           }

       })

   }

})

如此,connection.query() 是一個異步的操做,咱們在調用他的時候,不會立刻獲得結果,而是會繼續執行後面的代碼。這樣,若是咱們須要在查到結果以後才作某些事情的話,就須要把相關的代碼寫在回調裏面,若是涉及到多個這樣的異步操做,就勢必會陷入到回調地獄中去。

這種回調地獄不只看起來很不舒服,可讀性比較差;除此以外還有比較重要的一點就是對異常的捕獲沒法支持。

3.2 Promise

Promise 是 ES 2015 原生支持的,他把原來嵌套的回調改成了級聯的方式。

通常着,咱們對一個 Promise 能夠這樣寫:

var a = new Promise(function(resolve, reject) {

 setTimeout(function() {

     resolve('1')

 }, 2000)

})

a.then(function(val) {

   console.log(val)

})

若是要涉及到多個異步操做的順序執行問題,咱們能夠這樣寫:

var a = new Promise(function(resolve, reject) {

 setTimeout(function() {

     resolve('1')

 }, 2000)

})

 

a

 .then(function(val){

   console.log(val)

   return new Promise(function(resolve, reject) {

     setTimeout(function() {

         resolve('2')

     }, 2000)

   })

 })

 .then(function(val) {

   console.log(val)

 })

也能夠把函數抽離出來

var a = new Promise(function(resolve, reject) {

 setTimeout(function() {

     resolve('1')

 }, 2000)

})

 

 

function b(val) {

 console.log(val)

 

 return new Promise(function(resolve, reject) {

   setTimeout(function() {

       resolve('2')

   }, 2000)

 })

}

 

 

a.then(b).then(function(val) {

console.log(val)

})

咱們只須要 return 一個 Promise 便可實現這種多個異步操做的順序執行。

粗略來看,這是一個比較優雅的異步解決方案了,而且在 Promise 中咱們也能夠實現分級的 catch。

但對於以前接觸過其餘語言的同窗來講仍是比較彆扭的。那可否用同步的方式來書寫異步呢?

3.3 Generator

在 ES 2015 中,出現了 Generator 的語法,熟悉 Python 的同窗確定對這種語法有點了解。

簡單來講,Generator 能夠理解爲一個能夠遍歷的狀態機,調用 next 就能夠切換到下一個狀態。

在 JavaScript 中,Generator 的 function 與 函數名之間有一個 *, 函數內部使用 yield 關鍵詞,定義不一樣的狀態。

先看一段代碼:

function a() {

 return new Promise((resolve, reject) => {

   setTimeout(() => {

     resolve(1)

   }, 2000)

 });

};

 

var b = co(function *() {

   var val = yield a();

   console.log(val)

})

 

b()

上面的這段代碼是藉助 TJ 的 co 實現的,依照約定,co 中 yield 後面只能跟 Thunk 或者 Promise.

co 的實現代碼很短,簡單來講大致是這樣:

// http://www.alloyteam.com/2015/04/solve-callback-hell-with-generator/

 

function co(genFun) {

   // 經過調用生成器函數獲得一個生成器

   var gen = genFun();

   return function(fn) {

       next();

       function next(err, res) {

           if(err) return fn(err);

           // 將res傳給next,做爲上一個yield的返回值

           var ret = gen.next(res);

           // 若是函數還沒迭代玩,就繼續迭代

           if(!ret.done) return ret.value(next);

           // 返回函數最後的值

           fn && fn(null, res);

       }

   }

}

簡單來講就是一直藉助 generator 的 next 進行迭代,直到完成這個異步操做才返回。當前人家官方的 co 是 200 行代碼,支持異步操做的並行:

co(function *() {

   var val = yield [

       yield asyn1(),

       yield asyn2()

   ]

})()

但若是咱們使用 co,強迫症們就會以爲這不是標準的寫法,有點 hack 小子的感受。

幸運的是,在 ES 2016 的草案中,終於提出了標準的寫法。

3.4 await/async

這是在 ES 2016 中引入的新關鍵詞,這將在語言層面完全解決 JavaScript 的異步回調問題,目前能夠藉助 babel 在生產環境中使用。使用 await/async 可讓異步的操做以同步的方式來寫。

使用方法和 co 很是相似,同時也支持同步寫法的異常捕獲。

function a() {

 return new Promise((resolve, reject) => {

   setTimeout(() => {

     resolve(1)

   }, 2000)

 })

}

 

var b = async function() {

 var val = await a()

 console.log(val)

}

 

b()

若是上述的代碼徹底用 Promise 實現,極有多是下面的代碼:

function a() {

 return new Promise((resolve, reject) => {

   setTimeout(() => {

     resolve(1);

   }, 2000);

 });

};

 

var b = function() {

 a().then(val) {

   console.log(val)

 }

 console.log(val)

};

 

b();

相比較來講,await/async 解決了徹底使用 Promise 的一個極大痛點——不一樣Promise之間共享數據問題:

Promise 須要設定外層數據開始共享,這樣就須要在每一個then裏面進行賦值,而 await/async 就不存在這樣的問題,只須要以同步的方式去寫就能夠了。

await/async 對異常的支持也是特別好的:

function a() {

 return new Promise((resolve, reject) => {

   setTimeout(() => {

     resolve(1)

   }, 2000)

 });

};

 

var b = async function() {

   try {

       var val = await a()

       console.log(val)

   } catch (err) {

       console.log(err)

   }

};

 

b();

 

做者:rccoder

原文:https://github.com/rccoder/blog/issues/17

相關文章
相關標籤/搜索