Javascript異步編程之二回調函數

上一節講異步原理的時候基本上把回掉函數也捎帶講了一些,這節主要舉幾個例子來具體化一下。在開始以前,首先要明白一件事,在javascript裏函數能夠做爲參數進行傳遞,這裏涉及到高階函數的概念,你們能夠自行google一下。javascript

 
傳統的同步函數須要返回一個結果的話都是經過return語句實現,例如:
 
function foo() {
     var a = 3,
          b = 2;
     return a+b;
}

var c = foo();
console.log(c); //5
 
就是說後面的代碼console.log要獲得函數foo的運行結果只要調用該函數就能夠獲得它所返回的值a+b。
 
可是若是foo是一個異步函數,能夠這樣作嗎?
 
異步函數的定義:
 
首先說一下javascript裏面怎樣書寫異步函數。基本的方法就是,在你的函數定義裏面調用別人已經提供的異步api (不論是原生的仍是第三方的),你的函數也就是個異步函數了:
 
function foo(callback) {
     你本身的代碼;
     asyncFn(function() {
          var result = 你本身的代碼;
          callback(result);
     });
}

 

上面這個例子中,你要定義一個函數foo,裏面調用了一個異步函數asyncFn,在asyncFn運行完了以後調用foo的回調函數callback,來對結果result進行處理。
 
上面是通常異步函數的定義格式。setTimeout是javascript裏面常常用的異步api。 作試驗的時候常常用它來模擬異步操做,下面的例子模擬一個操做要運行1秒後才返回結果 (固然你能夠設0秒,但仍然是異步的):
 
function foo(callback) {
     你本身的代碼;
     setTimeout(function() {
          var result = 你本身的代碼;
          callback(result);
     }, 1000);
}
 
在node.js裏面提供了其餘的api來起到相似的做用(把你的同步代碼寫成異步函數),setImmediate或者process.nextTick,其做用就是異步調用你的代碼。這兩個基本上用法相似,但特定狀況下是不一樣的,能夠上網自行查找setImmediate,setTimeout(fn, 0)和process.nextTick 的區別,通常在不瞭解的狀況下,建議使用setImmediate.
 
function foo(callback) {
     你本身的代碼;
     setImmediate(function() {
          var result = 你本身的代碼;
          callback(result);
     });
}
 
function foo(callback) {
     你本身的代碼;
     process.nextTick(function() {
          var result = 你本身的代碼;
          callback(result);
     });
}

 

異步函數的調用:html

以上是異步函數的定義,下面講一下異步函數是怎樣調用的。在調用異步函數的時候,回調函數有兩種寫法,一是直接寫個匿名回調函數,下面例子中function(data) {...} 就是匿名回掉函數:
 
foo(function(data) {
     你的代碼來使用傳回來的data;
});
 
二是先定義一個函數,而後使用函數引用做爲回調:
 
function bar(data) {
     你的代碼來使用傳回來的data;
}

foo(bar);
 
如今回到開篇的問題,有同窗必定看明白了,爲何異步函數不能用return來返回值。 下面把兩種寫法放一塊兒,方便比較:
 
正確寫法:
function foo(callback) {
     你本身的代碼;
     asyncFn(function() {
          var result = 你本身的代碼;
          callback(result);
     });
}
 
錯誤寫法:
function bar() {
     你本身的代碼;
     asyncFn(function() {
          var result = 你本身的代碼;
     });
     
     return result;
}
 
在錯誤寫法中,bar企圖使用return來返回result。在上一篇已經講過,異步api不會等執行完了再往下執行,也就是說asyncFn在調用後立刻會往下執行return result這句,這時候asyncFn還在異步執行當中,result根本尚未計算出來,因此不能return指望結果。固然對javascript語法比較熟的同窗也清楚,函數外部不能訪問函數內部變量,就是說在asyncFn函數的外部是沒法訪問到result這個變量的,無論那個是主要緣由,哪一個是次要,異步api是確定不能用return來返回值。
 
以上就是基本的異步函數的定義和調用。
 
異步函數實例:
 
任何只講理論不講應用的教程都是耍流氓。。。好吧,舉個實際的例子:
 
網絡請求是node.js異步API中經常使用的一種,可以進行網絡請求的node.js原生和第三方的api很是多,下面以superagent爲例來演示一下(運行以前先用npm安裝superagent):
 
var agent = require('superagent');

agent.get('http://www.baidu.com')
     .end(function(err, res) {
          if(err) {
               console.log(err);
          } else {
               console.log('http status: ' + res.status);
               console.log(res.header);
          }
     });
 
上面代碼是能夠直接運行的。 這個例子中agent.get(url).end(callback)是一個異步api調用, 在回調函數裏面定義本身的代碼。 回掉函數傳回來兩個數據,err和res。 若是不出問題的話res對象是完整的http響應的內容,若是出錯的話出錯信息會保存在err對象中。
這裏爲了演示只是在控制檯打印傳回來的res的status和header (注:superagent是個很不錯的http客戶端,能夠去github瞭解其提供的具體功能)。
 
順帶提一下: 異步函數不能像同步代碼同樣用try...catch捕獲異常,因此這裏有一個約定俗成,就是回掉函數通常須要有兩個參數,上面superagent的例子中就是err和res,通常第一個參數是error,當異步代碼發生異常的時候用這個參數返回異常詳細信息, 第二個參數纔是返回的有用數據,當沒有異常的時候用它來返回,即superagent的例子中的res,返回的是http響應內容。
 
異步嵌套:
 
在同步代碼中,若是有三個函數順序執行,前一個的輸出做爲後一個的輸入,只要按順序執行三個函數便可。但在異步代碼中,要作到這個就有點麻煩了。具體的緣由就是由於不能像同步函數同樣用return來返回值,而必須用回掉函數。因此,第二個函數要在第一個函數的回調中調用,一樣第三個函數要在第二個函數的回調中調用,這就是所謂的回調嵌套。下面的例子是要先從a.txt讀取其內容,而後從b.txt讀取其內容,最後把兩個內容合併寫入ab.txt,完了後控制檯打印:read and write done!
 
var fs = require('fs');

fs.readFile('./a.txt', function(err1, data1) {
     fs.readFile('./b.txt', function(err2, data2) {
          fs.writeFile('./ab.txt', data1 + data2, function(err) {
               console.log('read and write done!');
          });
     });
});
 
三個異步函數嵌套看起來挺簡單的。設想一下,若是有5個,10個甚至更多的異步函數要順序執行,那要嵌套多少層呢?(你們都不喜歡身材橫着長吧哈哈)說實話至關恐怖,代碼會變得異常難讀,難調試,難維護。這就是所謂的回調地獄或者callback hell。正是爲了解決這個問題,纔有了後面兩節要講的內容,用promise或generator進行異步流程管理。異步流程管理說白了就是爲了解決回調地獄的問題。因此說任何事情都有兩面性,異步編程有它獨特的優點,卻也同時遇到了同步編程根本不會有的代碼組織難題。
 
思考題:
 
本身寫異步函數還有一種常常遇到的狀況,就是在循環中,好比for循環中不斷調用異步函數asyncFn,由於每次循環調用asyncFn你都不知道它會運行到何時,這種狀況下你的foo函數何時調用回掉函數來返回最終結果? 你們能夠用讀寫文件來試驗,使用fs.readFile這個函數來循環讀取某個目錄下面全部的文件內容,最後用fs.writeFile合併寫到一個新的文件裏面。答案下回分解 :)
 
function foo(arr, callback) {
     for(var i=0; i<arr.length; ++i) {
          asyncFn(function() {
               你的代碼;
          })
     }
}

 

上一篇: Javascript異步編程之一異步原理java

 

轉載請標明出處: http://www.cnblogs.com/chrischjh/p/4667713.htmlnode

相關文章
相關標籤/搜索