摘要:異步編程解決方案事件發布訂閱模式訂閱,發布事件監聽是一種高階函數的應用,通過事件可以把內部數據傳遞給外部的調用者,編程者可以不用關心組件內部如何執行,只需關注在需要的事件點上即可。
異步編程難點 異常處理
在處理異常時經常用try/catch/final語句塊進行異常捕獲,但是這種異常捕獲對異步編程并不是用
function async(callback) { process.nextTick(callback); } try { async(function () { console.log(a); }); } catch (err) { // TODO }
異步代碼分為兩個過程,提交請求和處理結果,其中代碼在異步處理完成之前返回,而異常不一定在這個過程中發生,所以try、catch不會有任何作用,調用async時,callback被暫時掛起,等到代碼執行完畢才會執行,try只能捕獲當前事件循環的異常,對下一次的事件循環無法處理(nodejs異步時間做了約定,異常一定被當成第一個參數傳回,在調用callback時先判斷是否有異常發生)
function async(callback) { process.nextTick(function () { if (err) { return callback(err); } callback(null); }); } try { async(function (err) { if (!err) { console.log(a); } }); } catch (err) { // TODO }函數嵌套過深
對于Node和agax調用而言,有時會存在多個異步調用嵌套的場景,比如一個文件目錄的遍歷操作:
fs.readdir(path.join(__dirname, ".."), function (err, file) { files.forEach(function (filename, index) { fs.readFile(filename, "utf8), function (err, file) { // TODO } }); });
或者一個網頁渲染操作:
$(selector).click(function (e) { $ajax({ data: "", success: function (data) { template.init(data, function (tpl) { // TODO }); } }); });
上面的代碼邏輯上是沒有問題的,但是并沒有利用好異步I/O帶來的優勢,這是異步編程的典型問題。
多線程編程如果是多核CPU,單個Node進程實際沒有充分利用多核CPU,瀏覽器提出了Web workers,通過將javascrit執行與UI渲染分離,可以良好的利用多核CPU。因為前端瀏覽器對標準的滯后,Web workers并沒有廣泛應用起來。
異步轉同步習慣同步編程的同學,并不能從容面對異步編程帶來的副產品,比如嵌套回調、業務分散。Node 提供了絕大部分異步 API 卻很少有同步 API,往往出現同步需求會無所適從,雖然 Node 試圖異步轉同步但是并沒有原生的支持,需要借助庫或者編譯實現,對于異步編程通過良好的流程控制,還是可以降落幾梳理成順序的形式。
異步編程解決方案 事件發布/訂閱模式// 訂閱 emiiiter.on("event", function(message) { console.log(message); }) // 發布 emitter.emit("event", "i am a message");
事件監聽是一種高階函數的應用,通過事件可以把內部數據傳遞給外部的調用者,編程者可以不用關心組件內部如何執行,只需關注在需要的事件點上即可。注意:
如果事件的監聽器過多可能出現過度占用cup的結果。
如果運行期間觸發了error事件,解釋器會檢查是否對error監聽了事件,如果有就交給監聽器處理,如果沒有則將錯誤拋出。所以應該對error事件做監聽。
利用事件可以解決雪崩問題:當大量的訪問同時發生時,服務器無法對所有的訪問做處理,可以在第一個回調添加狀態鎖控制服務器的訪問數量,同時使用事件(once)把所有請求壓入隊列中。
promise/deferred模式promise/A 規定了三種狀態,未完成態、完成態和失敗態,未完成態向其他兩種狀態轉化,不能逆轉;
pedding -> resolved
-> rejected
function call(state, fn, err, arg) { if (state === "pendding") { fn(arg); } else { fn(err); } } new Promise = function (fn) { this.state = "pendding"; this.fn = function() {}; return fn(this.resolve, this.reject); } Promise.prototype.then = function (fn) { this.fn = fn; return this; } Promise.prototype.resolve = function (arg) { this.state = "resolved"; call(this.state, this.fn, null, arg); return this; } Promise.prototype.reject = function () { this.state = "rejected"; var err = "err opened"; call(this.state, this.fn, err); return this; } new Promise(function (resolve, reject) { setTimeout(function () { var value = "abc"; resolve(value); }, 100); }).then(function (result) { console.log(result); });流程控制庫
使用connect存儲中間件手動調用執行的方式,例如next,通常叫做尾觸發,尾觸發在jquery中非常常見,比如
$get("/get").success().error();
這種方式首先注冊中間件,每個中間件包括傳遞請求對象,響應對象和尾觸發函數,通過隊列行程一個處理流,最簡單的中間例如:
function (req, res, err) { // 中間件 }
connect核心代碼:
function creatServer() { function app(req, res) { app.handle(req,res); } app.stack = []; for (var i = 0; i < arguments.length; ++i) { app.use(arguments[i]); } return app; }
app.use:
app.use = function(router, fn) { this.stack.push(fn); return this; }
next:
function handle = function() { // ... next(); } function next() { // ... next callback ... layer = this.stack[index++]; layer.handle(req, res, next); }
異步的串行執行
async.series([function (callback) { callback(); },function (callback) { callback(); }], function (err, result) {})
等價于:
function (callback) { function (callback) { callback(); } callback(); }
異步的并行執行:
async.parallel([function (callback) { callback(); }, function (callback) { callback(); }], function (err, results) { });
等價于:
var counter = 2; var results = []; var done = function (index, value) { results[index] = value; if (!--conuter) { callback(null, results); } } function (callback) { // var value = ... callback(); done(0, value); } function (callback) { // var value = ... callback(); done(1, value); }
依賴處理
當前一個異步的結果是后一個異步的輸入時,async使用waterfall方式處理 async.waterfall([function (callback) { callback(); }, function (arg1, callback) { callback(); }, function (arg2, callback) { callback(); }], function (err, results) { });
當存在很多依賴關系,有同步有異步時,async使用auto()實現復雜的處理
async.waterfall({ fun1:function (callback) { callback(); }, fun2: ["fun1", function (arg1, callback) { callback(); }, function (arg2, callback) { callback(); }]}, function (err, results) { });
step接受任意數量的任務,所有任務會串行執行:
step(task1, task2, task3);
step使用next把上一步的結果傳遞給下一步作為參數
在執行多個異步任務時,調用代碼如下:
step(function () { fn1(this.parallel()); fn2(this.parallel()); }, function (err, result1, result2) { });
wind旨在控制異步流程的邏輯控制,其作用類似generator:
eval(Wind.compile("async", funtion () { $await(Wind.Async.sleep(20)); //延遲20ms console.log("hello world"); }));generator generaor函數
function * maker(){ var index = 0; while (index < 10) { yield index++; } } var g = maker(); // 輸出結果 console.log(g.next().value); // 0 console.log(g.next().value); // 1 console.log(g.next().value); // 2yeild關鍵字
yield 關鍵字用來暫停和恢復一個生成器函數
[rv] = yield [expression]; yield [[expression]];
rv 返回傳遞給生成器的 next() 方法的可選值,以恢復其執行。
Regenerator上面這段代碼等價下面代碼:
var _marked = [maker].map(regeneratorRuntime.mark); function maker() { var index; return regeneratorRuntime.wrap(function maker$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: index = 0; case 1: if (!(index < 10)) { _context.next = 6; break; } _context.next = 4; return index++; case 4: _context.next = 1; break; case 6: case "end": return _context.stop(); } } }, _marked[0], this); } var g = maker(); console.log(g.next().value); // 0 console.log(g.next().value); // 1 console.log(g.next().value); // 2
編譯機制造了一個狀態機,通過_context.next狀態的裝換完成代碼執行的掛起。
假設狀態是0 -> n(n是最后一個狀態)
0運行第一個yield之前的所有代碼,n運行最后一個yield函數之后的所有代碼,generator的next尾調用通過一個while循環實現,如果_context.next到達最后一個case就退出循環,等待下一次next調用
regenerator是用來生成generetor函數并返回一個迭代器供外界調用的高階函數,功能主要是
regenerator-transform: 重寫generator函數把yield重寫成switch case,并且創建_context.next保存上下文環境;
包裝generator函數被返回一個迭代器對象;
經過wrap返回的迭代器:
GeneratorFunctionPrototype { _invoke: function invoke(method, arg) { … } __proto__: GeneratorFunctionPrototype { constructor: function GeneratorFunctionPrototype() {}, next: function (arg) { … }, throw: function (arg) { … } … } }
當調用迭代器對象iter.next()方法時,因為有如下代碼,所以會執行_invoke方法,而根據前面wrap方法代碼可知,最終是調用了迭代器對象的 makeInvokeMethod (innerFn, self, context); 方法
makeInvokeMethod方法內容較多,這里選取部分分析。
function makeInvokeMethod(innerFn, self, context) { var state = GenStateSuspendedStart; return function invoke(method, arg) {
makeInvokeMethod返回invoke函數,當我們執行.next方法時,實際調用的是invoke方法中的下面語句
var record = tryCatch(innerFn, self, context);
這里tryCatch方法中fn為經過轉換后的example$方法,arg為上下文對象context,因為invoke函數內部對context的引用形成閉包引用,所以context上下文得以在迭代期間一直保持。
function tryCatch(fn, obj, arg) { try { return { type: "normal", arg: fn.call(obj, arg) }; } catch (err) { return { type: "throw", arg: err }; } }
tryCatch方法會實際調用 example$ 方法,進入轉換后的switch case,執行代碼邏輯。如果得到的結果是一個普通類型的值,我們將它包裝成一個可迭代對象格式,并且更新生成器狀態至GenStateCompleted或者GenStateSuspendedYield
var record = tryCatch(innerFn, self, context); if (record.type === "normal") { // If an exception is thrown from innerFn, we leave state === // GenStateExecuting and loop back for another invocation. state = context.done ? GenStateCompleted : GenStateSuspendedYield; var info = { value: record.arg, done: context.done };
偽代碼:
function wrap(innerFn, outerFn, self, tryLocsList) { var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator; var generator = Object.create(protoGenerator.prototype); var context = new Context(tryLocsList || []); generator._invoke = makeInvokeMethod(innerFn, self, context); return generator; } function makeInvokeMethod(innerFn, self, context) { var obj = this; return function invoke(method, arg) { context.method = method; // 把next帶入的arg參數賦值給sent if (context.method === "next") { context.sent = context._sent = context.arg; } // 實際上調用了mark$,并且帶入了context var record = { arg: innerFn.call(obj, context) }; // 返回一個可以迭代的對象 return {value: record.arg, done: context.done}; }; } // 用一個next調用invoke, 如果要進行下一步就傳入next generator.next = next(arg) { generator._invoke("next", arg); }cojs處理generator過程
能夠得到一個函數的函數叫thunk函數, thunk函數是一個偏函數,它只帶一個執行參數
function getThunk(number) { return function (fn) { setTimeout(() => { if (number) { fn(null, number); } else { const err = "error open"; fn(err); } }, number) } }
import co from "co"; co(function * () { var a = yield getThunk(100); var b = yield getThunk(1000); console.log("a:", a); console.log("b:", b); return [a, b]; }) // 輸出 // a 100 // b 1000
function co2Thunk(fn) { return (done) => { const ctx = this; const g = fn.call(ctx); function next(err, res) { console.log("next1", res); let it = g.next(res); if (it.done) { done.call(ctx, err, it.value); } else { it.value(next); } } next(); } } co2Thunk(function * () { var a = yield getThunk(10000); var b = yield getThunk(1000); // console.log("a:", a); // console.log("b:", b); return [a, b]; })(function (err, args) { console.log("callback thunk co : =========="); // console.log(err, args); });
co2Thunk的代碼等價于:
function co2Thunk(fn) { return (done) => { const ctx = this; const g = fn.call(ctx); let it0 = g.next(); it0.value((err, res) => { const it1 = g.next(res); // 第一次迭代返回的是getThunk(10000); it0.value((err, res) => { const it1 = g.next(res); // 第二次迭代返回的是getThunk(1000); it1.value((err, res) => { const it2 = g.next(data); // ... }); }); }); } } // it.value 等價于: function (fn) { setTimeout(() => { if (number) { fn(null, number); } else { const err = "error open"; fn(err); } }, number) }
function co2Promise(fn) { return new Promise((resolve, reject) => { const ctx = this; const g = fn.call(ctx); function next(err, res) { let it = g.next(res); if (it.done) { resolve(it.value); } else { it.value(next); } } next(); }); } co2Promise(function * () { var a = yield getThunk(100); var b = yield getThunk(1000); console.log("a:", a); console.log("b:", b); return [a, b]; }).then(function (args) { console.log("callback promise co : =========="); console.log(args); });
function co2Thunk(fn) { return (done) => { const ctx = this; const g = fn.call(ctx); function next(err, res) { let it = g.next(res); if (it.done) { done.call(ctx, err, it.value); } else { // 增加對其他類型的處理 const value = toThunk.call(ctx, it.value); // 對于promise 此處應該是 value.then(next) value(next); } } next(); } } co2Thunk(function * () { var a = getThunk(100); var b = getThunk(1000); // console.log("a:", a); console.log("b:", b); return yield [a, b]; })(function (err, args) { console.log("callback thunk co : =========="); console.log(err, args); }); function toThunk(obj) { if (isObject(obj) || isArray(obj)) { return objectToThunk(obj); } if (isPromise(obj)) { return promiseToThunk.call(ctx, obj); } return obj; } function objectToThunk(obj) { return function (done) { let keys = Object.keys(obj); let length = keys.length; let results = new obj.constructor(); for(let key in keys) { const fn = toThunk(obj[key]); fn((err, res) => { results[key] = res; --length || done(null, results); }, key); } } } function promiseToThunk(promise){ return function(done){ promise.then(function(err,res){ done(err,res); },done) } } function isObject(obj) { return obj && Object == obj.constructor; } function isArray(obj) { return Array.isArray(obj); } function isPromise(obj) { return obj && "function" == typeof obj.then; }async/await
async function fn(args){ // ... }
等同于
function fn(args){ return co2Thunk(function*() { // ... }); }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84625.html
摘要:異步流程管理說白了就是為了解決回調地獄的問題。對象代表一個異步操作,有三種狀態進行中已成功和已失敗。如果改變已經發生了,你再對對象添加回調函數,也會立即得到這個結果。執行函數后返回的是一個遍歷器對象,可以依次遍歷函數內部的每一個狀態。 javascript -- 深度解析異步解決方案 高級語言層出不窮, 然而唯 js 鶴立雞群, 這要說道js的設計理念, js天生為異步而生, 正如布道...
摘要:學習開發,無論是前端開發還是都避免不了要接觸異步編程這個問題就和其它大多數以多線程同步為主的編程語言不同的主要設計是單線程異步模型。由于異步編程可以實現非阻塞的調用效果,引入異步編程自然就是順理成章的事情了。 學習js開發,無論是前端開發還是node.js,都避免不了要接觸異步編程這個問題,就和其它大多數以多線程同步為主的編程語言不同,js的主要設計是單線程異步模型。正因為js天生的與...
摘要:更好的異步編程上面的方法可以適用于那些比較簡單的異步工作流程。小結的組合目前是最強大,也是最優雅的異步流程管理編程方式。 訪問原文地址 generators主要作用就是提供了一種,單線程的,很像同步方法的編程風格,方便你把異步實現的那些細節藏在別處。這讓我們可以用一種很自然的方式書寫我們代碼中的流程和狀態邏輯,不再需要去遵循那些奇怪的異步編程風格。 換句話說,通過將我們generato...
摘要:因為瀏覽器環境里是單線程的,所以異步編程在前端領域尤為重要。除此之外,它還有兩個特性,使它可以作為異步編程的完整解決方案函數體內外的數據交換和錯誤處理機制。 showImg(https://segmentfault.com/img/bVz9Cy); 在我們日常編碼中,需要異步的場景很多,比如讀取文件內容、獲取遠程數據、發送數據到服務端等。因為瀏覽器環境里Javascript是單線程的,...
摘要:序在中,大家討論的最多的就是異步編程的操作,如何避免回調的多次嵌套。今天所講的和就是和異步編程有關,可以幫助我們把異步編程同步化。然而這樣的方法依然需要依賴外在的庫函數,于是中提出了和關鍵字。 序 在Javascript中,大家討論的最多的就是異步編程的操作,如何避免回調的多次嵌套。異步操作的回調一旦嵌套很多,不僅代碼會變的臃腫,還很容易出錯。各種各樣的異步編程解決方案也被不斷提出,例...
摘要:傳統的異步方法回調函數事件監聽發布訂閱之前寫過一篇關于的文章,里邊寫過關于異步的一些概念。內部函數就是的回調函數,函數首先把函數的指針指向函數的下一步方法,如果沒有,就把函數傳給函數屬性,否則直接退出。 Generator函數與異步編程 因為js是單線程語言,所以需要異步編程的存在,要不效率太低會卡死。 傳統的異步方法 回調函數 事件監聽 發布/訂閱 Promise 之前寫過一篇關...
閱讀 2261·2021-10-09 09:41
閱讀 3409·2021-09-13 10:34
閱讀 1920·2019-08-30 12:59
閱讀 557·2019-08-29 17:27
閱讀 1063·2019-08-29 16:07
閱讀 2956·2019-08-29 13:15
閱讀 1306·2019-08-29 13:14
閱讀 1562·2019-08-26 12:18