摘要:主線程從讀取回調函數(shù)并執(zhí)行。根據(jù)循環(huán)運行數(shù)一次異步操作的最大數(shù)量,,進入集合中第一個函數(shù)的調用,進入,注冊回調函數(shù)。事件完成,回調函數(shù)進入。采用同步功能并將其設置為異步,并將其返回值傳遞給回調函數(shù)。
前言
最近在看Node設計模式之異步編程的順序異步迭代,簡單的實現(xiàn)如下:
function series(tasks, callback) { let results = []; function iterate(index) { if (index === tasks.length) { return finish(); } const task = tasks[index]; task(function(err, res) { results.push(res); iterate(index + 1); }); } function finish() { // 迭代完成的操作 callback(null, results); } iterate(0); } series( [ callback => { setTimeout(function() { console.log(456); callback(null, 1); }, 500); }, callback => { console.log(123); callback(null, 2); } ], function(err, results) { console.log(results); } ); // 456 // 123 // [1, 2]
而async庫是一個非常流行的解決方案,在Node.js和JavaScript中來說,用于處理異步代碼。它提供了一組功能,可以大大簡化不同配置中一組任務的執(zhí)行,并為異步處理集合提供了有用的幫助。
async庫可以在實現(xiàn)復雜的異步控制流程時大大幫助我們,但是一個難題就是選擇正確的庫來解決問題。例如,對于順序執(zhí)行,有大約20個不同的函數(shù)可供選擇。
好奇心起來,就想看看一個成熟的庫跟我們簡單實現(xiàn)的代碼區(qū)別有多大。
series按順序運行任務集合中的函數(shù),每個函數(shù)在前一個函數(shù)完成后運行。如果系列中的任何函數(shù)將錯誤傳遞給其回調函數(shù),則不會運行更多函數(shù),并立即使用錯誤值調用回調函數(shù)。否則,回調會在任務完成時收到一系列結果。
const async = require("async"); async.series({ one: function(callback) { setTimeout(function() { callback(null, 1); }, 200); }, two: function(callback){ setTimeout(function() { callback(null, 2); }, 100); } }, function(err, results) { console.log(results); // results is now equal to: {one: 1, two: 2} });
我們來看看源碼,找到series方法,可以看到:
function series(tasks, callback) { _parallel(eachOfSeries, tasks, callback); }
除了我們自己傳的兩個參數(shù)以外,默認還傳了一個eachOfSeries,接著往下看:
function _parallel(eachfn, tasks, callback) { // noop:空的函數(shù) callback = callback || noop; // isArrayLike:檢查"value"是否與array相似 var results = isArrayLike(tasks) ? [] : {}; eachfn(tasks, function (task, key, callback) { // wrapAsync:包裝成異步 wrapAsync(task)(function (err, result) { if (arguments.length > 2) { result = slice(arguments, 1); } results[key] = result; callback(err); }); }, function (err) { callback(err, results); }); }
這里我們可以看到,_parallel方法其實就是eachOfSeries方法的調用。
先解釋一下eachOfSeries這三個參數(shù):
第一個參數(shù)就是要執(zhí)行的函數(shù)的集合。
第二個參數(shù)可以看成每個函數(shù)的執(zhí)行(wrapAsync可以先忽略掉,直接看成這一個函數(shù))。
第三個參數(shù)就是所有函數(shù)執(zhí)行完后的回調。
讓我們來看看eachOfSeries是如何的實現(xiàn):
var eachOfSeries = doLimit(eachOfLimit, 1); function eachOfLimit(coll, limit, iteratee, callback) { _eachOfLimit(limit)(coll, wrapAsync(iteratee), callback); } function doLimit(fn, limit) { return function (iterable, iteratee, callback) { return fn(iterable, limit, iteratee, callback); }; }
我們把上面進行轉換,這樣看起來更明了些:
var eachOfSeries = function(iterable, iteratee, callback) { return _eachOfLimit(1)(iterable, wrapAsync(iteratee), callback); };
Soga,最終就是調用_eachOfLimit完成的:
// limit:一次異步操作的最大數(shù)量,傳1可以看成串行,一個函數(shù)執(zhí)行完才進行下一個 function _eachOfLimit(limit) { return function (obj, iteratee, callback) { // once:函數(shù)只運行一次 callback = once(callback || noop); if (limit <= 0 || !obj) { return callback(null); } // iterator:迭代器,有根據(jù)類型分類,這邊簡單拿數(shù)組迭代器createArrayIterator來分析 var nextElem = iterator(obj); var done = false; var running = 0; var looping = false; function iterateeCallback(err, value) { running -= 1; if (err) { done = true; callback(err); } else if (value === breakLoop || (done && running <= 0)) { done = true; return callback(null); } else if (!looping) { replenish(); } } function replenish () { looping = true; while (running < limit && !done) { var elem = nextElem(); if (elem === null) { done = true; if (running <= 0) { callback(null); } return; } running += 1; // onlyOnce:函數(shù)只運行一次 iteratee(elem.value, elem.key, onlyOnce(iterateeCallback)); } looping = false; } // 遞歸 replenish(); }; } function once(fn) { return function() { if (fn === null) return; var callFn = fn; fn = null; callFn.apply(this, arguments); }; } // 閉包大法,拿取集合中的函數(shù) function createArrayIterator(coll) { var i = -1; var len = coll.length; return function next() { return ++i < len ? {value: coll[i], key: i} : null; } }
終于,看到series的真身了。實現(xiàn)其實就是replenish()的遞歸大法。因為要實現(xiàn)串行,所以在replenish()中控制running數(shù)為1,取出集合中一個函數(shù)執(zhí)行,然后回調iterateeCallback(),running數(shù)減1,再調用replenish(),這樣就能控制每個函數(shù)在前一個函數(shù)完成后運行。
說起來這流程還是比較簡單,但是在異步編程里還是不太好理解,我們先來了解一下js執(zhí)行機制,再舉一個例子來看:
同步的進入主線程,異步的進入Event Table并注冊函數(shù)。
當指定的事情完成時,Event Table會將這個函數(shù)移入Event Queue。
主線程內的任務執(zhí)行完畢為空,會去Event Queue讀取對應的函數(shù),進入主線程執(zhí)行。
上述過程會不斷重復,也就是常說的Event Loop(事件循環(huán))。
普通版
function a() { setTimeout(function() { console.log(456); }, 500); } function b() { console.log(123); } function c() { setTimeout(function() { console.log(789); }, 0); } a(); b(); c(); // 123 // 789 // 456
按順序執(zhí)行可以看到
a()中setTimeout進入Event Table,注冊回調函數(shù)。
b(),執(zhí)行console.log(123)。
c()中setTimeout進入Event Table,注冊回調函數(shù)。
c()中setTimeout先完成,回調函數(shù)進入Event Queue。
c()中setTimeout 500ms后完成,回調函數(shù)進入Event Queue。
主線程從Event Queue讀取回調函數(shù)并執(zhí)行。
series版
const async = require("async"); async.series( [ callback => { setTimeout(function() { console.log(456); callback(null, 1); }, 500); }, callback => { console.log(123); callback(null, 2); }, callback => { setTimeout(function() { console.log(789); callback(null, 3); }, 0); } ], function(err, results) { console.log(results); } ); // 456 // 123 // 789 // [ 2, 1, 3 ]
按我自己的理解,主線程和Event Loop都執(zhí)行完稱為一輪:
第一輪
按照上面流程,主線程走到_eachOfLimit(),調用replenish()。根據(jù)while循環(huán)(運行數(shù)running < 一次異步操作的最大數(shù)量 limit),running += 1,進入集合中第一個函setTimeout數(shù)的調用,setTimeout進入Event Table,注冊回調函數(shù)。
回到while循環(huán),running=limit,結束循環(huán),結束主線程。
setTimeout事件完成,回調函數(shù)進入Event Queue。
主線程從Event Queue讀取回調函數(shù)并執(zhí)行,回調iterateeCallback,running -= 1,調用replenish()。
第二輪
重復第一輪。只要的區(qū)別在于集合中的第二個函數(shù)是同步的,所有是主線程一路執(zhí)行下來。
第三輪
重復第一輪。
第四輪
集合中的三個函數(shù)已經都執(zhí)行完了,通過iterator()閉包拿到是null,回調最終結果。
function wrapAsync(asyncFn) { return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn; } var supportsSymbol = typeof Symbol === "function"; function isAsync(fn) { return supportsSymbol && fn[Symbol.toStringTag] === "AsyncFunction"; }
wrapAsync()先判斷是否異步函數(shù),如果是es7 Async Functions的話調用asyncify,否則返回原函數(shù)。
function asyncify(func) { return initialParams(function (args, callback) { var result; try { result = func.apply(this, args); } catch (e) { return callback(e); } // if result is Promise object if (isObject(result) && typeof result.then === "function") { result.then(function(value) { invokeCallback(callback, null, value); }, function(err) { invokeCallback(callback, err.message ? err : new Error(err)); }); } else { callback(null, result); } }); } var initialParams = function (fn) { return function (/*...args, callback*/) { var args = slice(arguments); var callback = args.pop(); fn.call(this, args, callback); }; };
采用同步功能并將其設置為異步,并將其返回值傳遞給回調函數(shù)。如果傳遞給asyncify的函數(shù)返回一個Promise,則該Promise的resolved/rejected狀態(tài)將用于調用回調,而不僅僅是同步返回值。
總結平日用慣async-await、promise,用起來簡單,但也導致缺少思考。而嘗試用原生js去模擬,閱讀源碼,卻能帶來更多的收獲。
github地址,喜歡的支持star一下,Thanks?(?ω?)?。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95792.html
摘要:目前這個爬蟲還是比較簡單的類型的,直接抓取頁面,然后在頁面中提取數(shù)據(jù),保存數(shù)據(jù)到數(shù)據(jù)庫??偨Y寫這個項目其實主要的難點在于程序穩(wěn)定性的控制,容錯機制的設置,以及錯誤的記錄,目前這個項目基本能夠實現(xiàn)直接運行一次性跑通整個流程。 前言 之前研究數(shù)據(jù),零零散散的寫過一些數(shù)據(jù)抓取的爬蟲,不過寫的比較隨意。有很多地方現(xiàn)在看起來并不是很合理 這段時間比較閑,本來是想給之前的項目做重構的。后來 利用這...
摘要:如果任何函數(shù)發(fā)生錯誤,會立刻執(zhí)行回調函數(shù),并返回錯誤信息若沒有發(fā)生錯誤,則會再所有函數(shù)執(zhí)行完畢之后用回掉函數(shù)將結果返回。 Async的簡單介紹: Async是一個流程控制工具包,提供了直接而強大的異步功能?;贘avascript為Node.js設計,同時也可以直接在瀏覽器中使用。Async提供了大約20個函數(shù),包括常用的map, reduce, filter, forEach等,異步...
摘要:面對著線程相關的問題,出現(xiàn)了協(xié)程。協(xié)程的特點在于是一個線程執(zhí)行,因此最大的優(yōu)勢就是協(xié)程極高的執(zhí)行效率。因為子程序切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,和多線程比,線程數(shù)量越多,協(xié)程的性能優(yōu)勢就越明顯。 Node的異步概念 理解異步非阻塞 提到Node,異步非阻塞會是第一個需要你理解的概念。很多人會把這實際上是兩個概念的詞混為一談,認為異步就是非阻塞的,而同步就...
摘要:編寫異步代碼可能是一種不同的體驗,尤其是對異步控制流而言?;卣{函數(shù)的準則在編寫異步代碼時,要記住的第一個規(guī)則是在定義回調時不要濫用閉包。為回調創(chuàng)建命名函數(shù),避免使用閉包,并將中間結果作為參數(shù)傳遞。 本系列文章為《Node.js Design Patterns Second Edition》的原文翻譯和讀書筆記,在GitHub連載更新,同步翻譯版鏈接。 歡迎關注我的專欄,之后的博文將在專...
摘要:是下的一個優(yōu)秀的框架,但是使用后,在流量增長時,進程有時突然內存暴漲保持高占用。如果是內存泄露引起的,則需要細心檢查代碼,確定變量能正常回收。每個對象有自己產生的內存。譯注但是大對象內存區(qū)本身不是可執(zhí)行的內存區(qū)。 Sails.js 是 node 下的一個優(yōu)秀的 MVC 框架,但是使用 Sails 后,在流量增長時, node 進程有時突然內存暴漲、保持高占用。經過翻閱源碼后,發(fā)現(xiàn)這個問...
閱讀 2056·2021-11-22 13:52
閱讀 980·2021-11-17 09:33
閱讀 2710·2021-09-01 10:49
閱讀 2848·2019-08-30 15:53
閱讀 2662·2019-08-29 16:10
閱讀 2437·2019-08-29 11:31
閱讀 1355·2019-08-26 11:40
閱讀 1868·2019-08-26 10:59