Javascript非同步編程之二回呼函數

來源:互聯網
上載者:User

標籤:

上一節講非同步原理的時候基本上把回掉函數也捎帶講了一些,這節主要舉幾個例子來具體化一下。在開始之前,首先要明白一件事,在javascript裡函數可以作為參數進行傳遞,這裡涉及到高階函數的概念,大家可以自行google一下。

 傳統的同步函數需要返回一個結果的話都是通過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);     });}

 

非同步函數的調用:

以上是非同步函數的定義,下面講一下非同步函數是怎樣調用的。在調用非同步函數的時候,回呼函數有兩種寫法,一是直接寫個匿名回呼函數,下面例子中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非同步編程之一非同步原理

 

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

Javascript非同步編程之二回呼函數

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.