最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

剖析Node.js異步編程中的回調(diào)與代碼設(shè)計模式

 更新時間:2016年02月16日 16:27:08   投稿:goldensun  
這篇文章主要介紹了Node.js異步編程中的回調(diào)與代碼設(shè)計模式,雖然大多數(shù)場合回調(diào)編寫時的長串括號不怎么好看,但Node的異步性能確實很好,需要的朋友可以參考下

NodeJS 最大的賣點——事件機制和異步 IO,對開發(fā)者并不是透明的。開發(fā)者需要按異步方式編寫代碼才用得上這個賣點,而這一點也遭到了一些 NodeJS 反對者的抨擊。但不管怎樣,異步編程確實是 NodeJS 最大的特點,沒有掌握異步編程就不能說是真正學會了 NodeJS。本章將介紹與異步編程相關(guān)的各種知識。

在代碼中,異步編程的直接體現(xiàn)就是回調(diào)。異步編程依托于回調(diào)來實現(xiàn),但不能說使用了回調(diào)后程序就異步化了。我們首先可以看看以下代碼。

function heavyCompute(n, callback) {
 var count = 0,
  i, j;

 for (i = n; i > 0; --i) {
  for (j = n; j > 0; --j) {
   count += 1;
  }
 }

 callback(count);
}

heavyCompute(10000, function (count) {
 console.log(count);
});

console.log('hello');

100000000
hello

可以看到,以上代碼中的回調(diào)函數(shù)仍然先于后續(xù)代碼執(zhí)行。JS 本身是單線程運行的,不可能在一段代碼還未結(jié)束運行時去運行別的代碼,因此也就不存在異步執(zhí)行的概念。

但是,如果某個函數(shù)做的事情是創(chuàng)建一個別的線程或進程,并與JS主線程并行地做一些事情,并在事情做完后通知 JS 主線程,那情況又不一樣了。我們接著看看以下代碼。

setTimeout(function () {
 console.log('world');
}, 1000);

console.log('hello');

hello
world


這次可以看到,回調(diào)函數(shù)后于后續(xù)代碼執(zhí)行了。如同上邊所說,JS 本身是單線程的,無法異步執(zhí)行,因此我們可以認為 setTimeout 這類 JS 規(guī)范之外的由運行環(huán)境提供的特殊函數(shù)做的事情是創(chuàng)建一個平行線程后立即返回,讓 JS 主進程可以接著執(zhí)行后續(xù)代碼,并在收到平行進程的通知后再執(zhí)行回調(diào)函數(shù)。除了 setTimeout、setInterval 這些常見的,這類函數(shù)還包括 NodeJS 提供的諸如 fs.readFile 之類的異步 API。

另外,我們?nèi)匀换氐?JS 是單線程運行的這個事實上,這決定了 JS 在執(zhí)行完一段代碼之前無法執(zhí)行包括回調(diào)函數(shù)在內(nèi)的別的代碼。也就是說,即使平行線程完成工作了,通知 JS 主線程執(zhí)行回調(diào)函數(shù)了,回調(diào)函數(shù)也要等到 JS 主線程空閑時才能開始執(zhí)行。以下就是這么一個例子。

function heavyCompute(n) {
 var count = 0,
  i, j;

 for (i = n; i > 0; --i) {
  for (j = n; j > 0; --j) {
   count += 1;
  }
 }
}

var t = new Date();

setTimeout(function () {
 console.log(new Date() - t);
}, 1000);

heavyCompute(50000);

8520


可以看到,本來應(yīng)該在1秒后被調(diào)用的回調(diào)函數(shù)因為 JS 主線程忙于運行其它代碼,實際執(zhí)行時間被大幅延遲。

代碼設(shè)計模式
異步編程有很多特有的代碼設(shè)計模式,為了實現(xiàn)同樣的功能,使用同步方式和異步方式編寫的代碼會有很大差異。以下分別介紹一些常見的模式。

函數(shù)返回值
使用一個函數(shù)的輸出作為另一個函數(shù)的輸入是很常見的需求,在同步方式下一般按以下方式編寫代碼:

var output = fn1(fn2('input'));
// Do something.

而在異步方式下,由于函數(shù)執(zhí)行結(jié)果不是通過返回值,而是通過回調(diào)函數(shù)傳遞,因此一般按以下方式編寫代碼:

fn2('input', function (output2) {
 fn1(output2, function (output1) {
  // Do something.
 });
});

可以看到,這種方式就是一個回調(diào)函數(shù)套一個回調(diào)函多,套得太多了很容易寫出>形狀的代碼。

遍歷數(shù)組
在遍歷數(shù)組時,使用某個函數(shù)依次對數(shù)據(jù)成員做一些處理也是常見的需求。如果函數(shù)是同步執(zhí)行的,一般就會寫出以下代碼:

var len = arr.length,
 i = 0;

for (; i < len; ++i) {
 arr[i] = sync(arr[i]);
}

// All array items have processed.

如果函數(shù)是異步執(zhí)行的,以上代碼就無法保證循環(huán)結(jié)束后所有數(shù)組成員都處理完畢了。如果數(shù)組成員必須一個接一個串行處理,則一般按照以下方式編寫異步代碼:

(function next(i, len, callback) {
 if (i < len) {
  async(arr[i], function (value) {
   arr[i] = value;
   next(i + 1, len, callback);
  });
 } else {
  callback();
 }
}(0, arr.length, function () {
 // All array items have processed.
}));

可以看到,以上代碼在異步函數(shù)執(zhí)行一次并返回執(zhí)行結(jié)果后才傳入下一個數(shù)組成員并開始下一輪執(zhí)行,直到所有數(shù)組成員處理完畢后,通過回調(diào)的方式觸發(fā)后續(xù)代碼的執(zhí)行。

如果數(shù)組成員可以并行處理,但后續(xù)代碼仍然需要所有數(shù)組成員處理完畢后才能執(zhí)行的話,則異步代碼會調(diào)整成以下形式:

(function (i, len, count, callback) {
 for (; i < len; ++i) {
  (function (i) {
   async(arr[i], function (value) {
    arr[i] = value;
    if (++count === len) {
     callback();
    }
   });
  }(i));
 }
}(0, arr.length, 0, function () {
 // All array items have processed.
}));

可以看到,與異步串行遍歷的版本相比,以上代碼并行處理所有數(shù)組成員,并通過計數(shù)器變量來判斷什么時候所有數(shù)組成員都處理完畢了。

異常處理
JS 自身提供的異常捕獲和處理機制——try..catch..,只能用于同步執(zhí)行的代碼。以下是一個例子。

function sync(fn) {
 return fn();
}

try {
 sync(null);
 // Do something.
} catch (err) {
 console.log('Error: %s', err.message);
}

Error: object is not a function

可以看到,異常會沿著代碼執(zhí)行路徑一直冒泡,直到遇到第一個 try 語句時被捕獲住。但由于異步函數(shù)會打斷代碼執(zhí)行路徑,異步函數(shù)執(zhí)行過程中以及執(zhí)行之后產(chǎn)生的異常冒泡到執(zhí)行路徑被打斷的位置時,如果一直沒有遇到 try 語句,就作為一個全局異常拋出。以下是一個例子。

function async(fn, callback) {
 // Code execution path breaks here.
 setTimeout(function () {
  callback(fn());
 }, 0);
}

try {
 async(null, function (data) {
  // Do something.
 });
} catch (err) {
 console.log('Error: %s', err.message);
}

/home/user/test.js:4
  callback(fn());
     ^
TypeError: object is not a function
 at null._onTimeout (/home/user/test.js:4:13)
 at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)

因為代碼執(zhí)行路徑被打斷了,我們就需要在異常冒泡到斷點之前用 try 語句把異常捕獲住,并通過回調(diào)函數(shù)傳遞被捕獲的異常。于是我們可以像下邊這樣改造上邊的例子。

function async(fn, callback) {
 // Code execution path breaks here.
 setTimeout(function () {
  try {
   callback(null, fn());
  } catch (err) {
   callback(err);
  }
 }, 0);
}

async(null, function (err, data) {
 if (err) {
  console.log('Error: %s', err.message);
 } else {
  // Do something.
 }
});

Error: object is not a function

可以看到,異常再次被捕獲住了。在 NodeJS 中,幾乎所有異步 API 都按照以上方式設(shè)計,回調(diào)函數(shù)中第一個參數(shù)都是 err。因此我們在編寫自己的異步函數(shù)時,也可以按照這種方式來處理異常,與 NodeJS 的設(shè)計風格保持一致。

有了異常處理方式后,我們接著可以想一想一般我們是怎么寫代碼的?;旧?,我們的代碼都是做一些事情,然后調(diào)用一個函數(shù),然后再做一些事情,然后再調(diào)用一個函數(shù),如此循環(huán)。如果我們寫的是同步代碼,只需要在代碼入口點寫一個 try 語句就能捕獲所有冒泡上來的異常,示例如下。

function main() {
 // Do something.
 syncA();
 // Do something.
 syncB();
 // Do something.
 syncC();
}

try {
 main();
} catch (err) {
 // Deal with exception.
}

但是,如果我們寫的是異步代碼,就只有呵呵了。由于每次異步函數(shù)調(diào)用都會打斷代碼執(zhí)行路徑,只能通過回調(diào)函數(shù)來傳遞異常,于是我們就需要在每個回調(diào)函數(shù)里判斷是否有異常發(fā)生,于是只用三次異步函數(shù)調(diào)用,就會產(chǎn)生下邊這種代碼。

function main(callback) {
 // Do something.
 asyncA(function (err, data) {
  if (err) {
   callback(err);
  } else {
   // Do something
   asyncB(function (err, data) {
    if (err) {
     callback(err);
    } else {
     // Do something
     asyncC(function (err, data) {
      if (err) {
       callback(err);
      } else {
       // Do something
       callback(null);
      }
     });
    }
   });
  }
 });
}

main(function (err) {
 if (err) {
  // Deal with exception.
 }
});

可以看到,回調(diào)函數(shù)已經(jīng)讓代碼變得復(fù)雜了,而異步方式下對異常的處理更加劇了代碼的復(fù)雜度。

相關(guān)文章

  • 淺談express 中間件機制及實現(xiàn)原理

    淺談express 中間件機制及實現(xiàn)原理

    本篇文章主要介紹了淺談express 中間件機制及實現(xiàn)原理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • 在node.js中怎么屏蔽掉favicon.ico的請求

    在node.js中怎么屏蔽掉favicon.ico的請求

    這篇文章主要介紹了在node.js中怎么屏蔽掉favicon.ico的請求,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2017-03-03
  • Node.js包管理器npm的具體使用

    Node.js包管理器npm的具體使用

    Node.js安裝包中都帶有一個重要的工具 包管理器npm,本文主要介紹了Node.js包管理器npm的具體使用,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • nodejs入門教程一:概念與用法簡介

    nodejs入門教程一:概念與用法簡介

    這篇文章主要介紹了nodejs入門教程之概念與用法,結(jié)合實例形式分析了nodejs基本概念、功能、命令與相關(guān)使用技巧,需要的朋友可以參考下
    2017-04-04
  • node.js express安裝及示例網(wǎng)站搭建方法(分享)

    node.js express安裝及示例網(wǎng)站搭建方法(分享)

    下面小編就為大家?guī)硪黄猲ode.js express安裝及示例網(wǎng)站搭建方法(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-08-08
  • 利用node.js+mongodb如何搭建一個簡單登錄注冊的功能詳解

    利用node.js+mongodb如何搭建一個簡單登錄注冊的功能詳解

    這篇文章主要給大家介紹了關(guān)于利用node.js+mongodb如何搭建一個簡單登錄注冊功能的相關(guān)資料,文中通過示例代碼介紹非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面跟著小編來一起學習學習吧。
    2017-07-07
  • 在Mac下徹底卸載node和npm的方法

    在Mac下徹底卸載node和npm的方法

    今天小編就為大家分享一篇在Mac下徹底卸載node和npm的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • 教你一招解決node-sass安裝各種坑

    教你一招解決node-sass安裝各種坑

    這篇文章主要給大家介紹了關(guān)于如何通過一招解決node-sass安裝各種坑的相關(guān)資料,Node-sass是一個庫,它將Node.js綁定到LibSass(流行樣式表預(yù)處理器Sass的C版本),需要的朋友可以參考下
    2023-12-12
  • nodejs簡單訪問及操作mysql數(shù)據(jù)庫的方法示例

    nodejs簡單訪問及操作mysql數(shù)據(jù)庫的方法示例

    這篇文章主要介紹了nodejs簡單訪問及操作mysql數(shù)據(jù)庫的方法,結(jié)合實例形式分析了nodejs創(chuàng)建mysql連接、執(zhí)行sql語句及關(guān)閉連接等相關(guān)操作技巧,需要的朋友可以參考下
    2018-03-03
  • 輕松創(chuàng)建nodejs服務(wù)器(8):非阻塞是如何實現(xiàn)的

    輕松創(chuàng)建nodejs服務(wù)器(8):非阻塞是如何實現(xiàn)的

    這篇文章主要介紹了輕松創(chuàng)建nodejs服務(wù)器(8):非阻塞是如何實現(xiàn)的,本文著重分析非阻塞的實現(xiàn),對代碼進行了分解,需要的朋友可以參考下
    2014-12-12

最新評論

大安市| 夹江县| 沁源县| 项城市| 军事| 济宁市| 资阳市| 呼和浩特市| 金乡县| 麟游县| 洞头县| 罗江县| 迭部县| 沽源县| 佛教| 巴彦淖尔市| 班玛县| 谢通门县| 南涧| 庆元县| 尉氏县| 偏关县| 大港区| 大姚县| 嘉黎县| 湖口县| 东阳市| 宜阳县| 昌图县| 黑山县| 怀柔区| 弥渡县| 大城县| 米林县| 正阳县| 阿拉善左旗| 康马县| 满洲里市| 东山县| 桑植县| 张家界市|