摘要:異步編程解決方案事件發(fā)布訂閱模式訂閱,發(fā)布事件監(jiān)聽是一種高階函數(shù)的應(yīng)用,通過事件可以把內(nèi)部數(shù)據(jù)傳遞給外部的調(diào)用者,編程者可以不用關(guān)心組件內(nèi)部如何執(zhí)行,只需關(guān)注在需要的事件點上即可。
異步編程難點 異常處理
在處理異常時經(jīng)常用try/catch/final語句塊進行異常捕獲,但是這種異常捕獲對異步編程并不是用
function async(callback) { process.nextTick(callback); } try { async(function () { console.log(a); }); } catch (err) { // TODO }
異步代碼分為兩個過程,提交請求和處理結(jié)果,其中代碼在異步處理完成之前返回,而異常不一定在這個過程中發(fā)生,所以try、catch不會有任何作用,調(diào)用async時,callback被暫時掛起,等到代碼執(zhí)行完畢才會執(zhí)行,try只能捕獲當前事件循環(huán)的異常,對下一次的事件循環(huán)無法處理(nodejs異步時間做了約定,異常一定被當成第一個參數(shù)傳回,在調(diào)用callback時先判斷是否有異常發(fā)生)
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 }函數(shù)嵌套過深
對于Node和agax調(diào)用而言,有時會存在多個異步調(diào)用嵌套的場景,比如一個文件目錄的遍歷操作:
fs.readdir(path.join(__dirname, ".."), function (err, file) { files.forEach(function (filename, index) { fs.readFile(filename, "utf8), function (err, file) { // TODO } }); });
或者一個網(wǎng)頁渲染操作:
$(selector).click(function (e) { $ajax({ data: "", success: function (data) { template.init(data, function (tpl) { // TODO }); } }); });
上面的代碼邏輯上是沒有問題的,但是并沒有利用好異步I/O帶來的優(yōu)勢,這是異步編程的典型問題。
多線程編程如果是多核CPU,單個Node進程實際沒有充分利用多核CPU,瀏覽器提出了Web workers,通過將javascrit執(zhí)行與UI渲染分離,可以良好的利用多核CPU。因為前端瀏覽器對標準的滯后,Web workers并沒有廣泛應(yīng)用起來。
異步轉(zhuǎn)同步習慣同步編程的同學,并不能從容面對異步編程帶來的副產(chǎn)品,比如嵌套回調(diào)、業(yè)務(wù)分散。Node 提供了絕大部分異步 API 卻很少有同步 API,往往出現(xiàn)同步需求會無所適從,雖然 Node 試圖異步轉(zhuǎn)同步但是并沒有原生的支持,需要借助庫或者編譯實現(xiàn),對于異步編程通過良好的流程控制,還是可以降落幾梳理成順序的形式。
異步編程解決方案 事件發(fā)布/訂閱模式// 訂閱 emiiiter.on("event", function(message) { console.log(message); }) // 發(fā)布 emitter.emit("event", "i am a message");
事件監(jiān)聽是一種高階函數(shù)的應(yīng)用,通過事件可以把內(nèi)部數(shù)據(jù)傳遞給外部的調(diào)用者,編程者可以不用關(guān)心組件內(nèi)部如何執(zhí)行,只需關(guān)注在需要的事件點上即可。注意:
如果事件的監(jiān)聽器過多可能出現(xiàn)過度占用cup的結(jié)果。
如果運行期間觸發(fā)了error事件,解釋器會檢查是否對error監(jiān)聽了事件,如果有就交給監(jiān)聽器處理,如果沒有則將錯誤拋出。所以應(yīng)該對error事件做監(jiān)聽。
利用事件可以解決雪崩問題:當大量的訪問同時發(fā)生時,服務(wù)器無法對所有的訪問做處理,可以在第一個回調(diào)添加狀態(tài)鎖控制服務(wù)器的訪問數(shù)量,同時使用事件(once)把所有請求壓入隊列中。
promise/deferred模式promise/A 規(guī)定了三種狀態(tài),未完成態(tài)、完成態(tài)和失敗態(tài),未完成態(tài)向其他兩種狀態(tài)轉(zhuǎn)化,不能逆轉(zhuǎn);
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存儲中間件手動調(diào)用執(zhí)行的方式,例如next,通常叫做尾觸發(fā),尾觸發(fā)在jquery中非常常見,比如
$get("/get").success().error();
這種方式首先注冊中間件,每個中間件包括傳遞請求對象,響應(yīng)對象和尾觸發(fā)函數(shù),通過隊列行程一個處理流,最簡單的中間例如:
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); }
異步的串行執(zhí)行
async.series([function (callback) { callback(); },function (callback) { callback(); }], function (err, result) {})
等價于:
function (callback) { function (callback) { callback(); } callback(); }
異步的并行執(zhí)行:
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); }
依賴處理
當前一個異步的結(jié)果是后一個異步的輸入時,async使用waterfall方式處理 async.waterfall([function (callback) { callback(); }, function (arg1, callback) { callback(); }, function (arg2, callback) { callback(); }], function (err, results) { });
當存在很多依賴關(guān)系,有同步有異步時,async使用auto()實現(xiàn)復(fù)雜的處理
async.waterfall({ fun1:function (callback) { callback(); }, fun2: ["fun1", function (arg1, callback) { callback(); }, function (arg2, callback) { callback(); }]}, function (err, results) { });
step接受任意數(shù)量的任務(wù),所有任務(wù)會串行執(zhí)行:
step(task1, task2, task3);
step使用next把上一步的結(jié)果傳遞給下一步作為參數(shù)
在執(zhí)行多個異步任務(wù)時,調(diào)用代碼如下:
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函數(shù)
function * maker(){ var index = 0; while (index < 10) { yield index++; } } var g = maker(); // 輸出結(jié)果 console.log(g.next().value); // 0 console.log(g.next().value); // 1 console.log(g.next().value); // 2yeild關(guān)鍵字
yield 關(guān)鍵字用來暫停和恢復(fù)一個生成器函數(shù)
[rv] = yield [expression]; yield [[expression]];
rv 返回傳遞給生成器的 next() 方法的可選值,以恢復(fù)其執(zhí)行。
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
編譯機制造了一個狀態(tài)機,通過_context.next狀態(tài)的裝換完成代碼執(zhí)行的掛起。
假設(shè)狀態(tài)是0 -> n(n是最后一個狀態(tài))
0運行第一個yield之前的所有代碼,n運行最后一個yield函數(shù)之后的所有代碼,generator的next尾調(diào)用通過一個while循環(huán)實現(xiàn),如果_context.next到達最后一個case就退出循環(huán),等待下一次next調(diào)用
regenerator是用來生成generetor函數(shù)并返回一個迭代器供外界調(diào)用的高階函數(shù),功能主要是
regenerator-transform: 重寫generator函數(shù)把yield重寫成switch case,并且創(chuàng)建_context.next保存上下文環(huán)境;
包裝generator函數(shù)被返回一個迭代器對象;
經(jīng)過wrap返回的迭代器:
GeneratorFunctionPrototype { _invoke: function invoke(method, arg) { … } __proto__: GeneratorFunctionPrototype { constructor: function GeneratorFunctionPrototype() {}, next: function (arg) { … }, throw: function (arg) { … } … } }
當調(diào)用迭代器對象iter.next()方法時,因為有如下代碼,所以會執(zhí)行_invoke方法,而根據(jù)前面wrap方法代碼可知,最終是調(diào)用了迭代器對象的 makeInvokeMethod (innerFn, self, context); 方法
makeInvokeMethod方法內(nèi)容較多,這里選取部分分析。
function makeInvokeMethod(innerFn, self, context) { var state = GenStateSuspendedStart; return function invoke(method, arg) {
makeInvokeMethod返回invoke函數(shù),當我們執(zhí)行.next方法時,實際調(diào)用的是invoke方法中的下面語句
var record = tryCatch(innerFn, self, context);
這里tryCatch方法中fn為經(jīng)過轉(zhuǎn)換后的example$方法,arg為上下文對象context,因為invoke函數(shù)內(nèi)部對context的引用形成閉包引用,所以context上下文得以在迭代期間一直保持。
function tryCatch(fn, obj, arg) { try { return { type: "normal", arg: fn.call(obj, arg) }; } catch (err) { return { type: "throw", arg: err }; } }
tryCatch方法會實際調(diào)用 example$ 方法,進入轉(zhuǎn)換后的switch case,執(zhí)行代碼邏輯。如果得到的結(jié)果是一個普通類型的值,我們將它包裝成一個可迭代對象格式,并且更新生成器狀態(tài)至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參數(shù)賦值給sent if (context.method === "next") { context.sent = context._sent = context.arg; } // 實際上調(diào)用了mark$,并且?guī)肓薱ontext var record = { arg: innerFn.call(obj, context) }; // 返回一個可以迭代的對象 return {value: record.arg, done: context.done}; }; } // 用一個next調(diào)用invoke, 如果要進行下一步就傳入next generator.next = next(arg) { generator._invoke("next", arg); }cojs處理generator過程
能夠得到一個函數(shù)的函數(shù)叫thunk函數(shù), thunk函數(shù)是一個偏函數(shù),它只帶一個執(zhí)行參數(shù)
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 此處應(yīng)該是 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*() { // ... }); }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/84625.html
摘要:異步流程管理說白了就是為了解決回調(diào)地獄的問題。對象代表一個異步操作,有三種狀態(tài)進行中已成功和已失敗。如果改變已經(jīng)發(fā)生了,你再對對象添加回調(diào)函數(shù),也會立即得到這個結(jié)果。執(zhí)行函數(shù)后返回的是一個遍歷器對象,可以依次遍歷函數(shù)內(nèi)部的每一個狀態(tài)。 javascript -- 深度解析異步解決方案 高級語言層出不窮, 然而唯 js 鶴立雞群, 這要說道js的設(shè)計理念, js天生為異步而生, 正如布道...
摘要:學習開發(fā),無論是前端開發(fā)還是都避免不了要接觸異步編程這個問題就和其它大多數(shù)以多線程同步為主的編程語言不同的主要設(shè)計是單線程異步模型。由于異步編程可以實現(xiàn)非阻塞的調(diào)用效果,引入異步編程自然就是順理成章的事情了。 學習js開發(fā),無論是前端開發(fā)還是node.js,都避免不了要接觸異步編程這個問題,就和其它大多數(shù)以多線程同步為主的編程語言不同,js的主要設(shè)計是單線程異步模型。正因為js天生的與...
摘要:更好的異步編程上面的方法可以適用于那些比較簡單的異步工作流程。小結(jié)的組合目前是最強大,也是最優(yōu)雅的異步流程管理編程方式。 訪問原文地址 generators主要作用就是提供了一種,單線程的,很像同步方法的編程風格,方便你把異步實現(xiàn)的那些細節(jié)藏在別處。這讓我們可以用一種很自然的方式書寫我們代碼中的流程和狀態(tài)邏輯,不再需要去遵循那些奇怪的異步編程風格。 換句話說,通過將我們generato...
摘要:因為瀏覽器環(huán)境里是單線程的,所以異步編程在前端領(lǐng)域尤為重要。除此之外,它還有兩個特性,使它可以作為異步編程的完整解決方案函數(shù)體內(nèi)外的數(shù)據(jù)交換和錯誤處理機制。 showImg(https://segmentfault.com/img/bVz9Cy); 在我們?nèi)粘>幋a中,需要異步的場景很多,比如讀取文件內(nèi)容、獲取遠程數(shù)據(jù)、發(fā)送數(shù)據(jù)到服務(wù)端等。因為瀏覽器環(huán)境里Javascript是單線程的,...
摘要:序在中,大家討論的最多的就是異步編程的操作,如何避免回調(diào)的多次嵌套。今天所講的和就是和異步編程有關(guān),可以幫助我們把異步編程同步化。然而這樣的方法依然需要依賴外在的庫函數(shù),于是中提出了和關(guān)鍵字。 序 在Javascript中,大家討論的最多的就是異步編程的操作,如何避免回調(diào)的多次嵌套。異步操作的回調(diào)一旦嵌套很多,不僅代碼會變的臃腫,還很容易出錯。各種各樣的異步編程解決方案也被不斷提出,例...
摘要:傳統(tǒng)的異步方法回調(diào)函數(shù)事件監(jiān)聽發(fā)布訂閱之前寫過一篇關(guān)于的文章,里邊寫過關(guān)于異步的一些概念。內(nèi)部函數(shù)就是的回調(diào)函數(shù),函數(shù)首先把函數(shù)的指針指向函數(shù)的下一步方法,如果沒有,就把函數(shù)傳給函數(shù)屬性,否則直接退出。 Generator函數(shù)與異步編程 因為js是單線程語言,所以需要異步編程的存在,要不效率太低會卡死。 傳統(tǒng)的異步方法 回調(diào)函數(shù) 事件監(jiān)聽 發(fā)布/訂閱 Promise 之前寫過一篇關(guān)...
閱讀 2270·2021-10-09 09:41
閱讀 3418·2021-09-13 10:34
閱讀 1929·2019-08-30 12:59
閱讀 567·2019-08-29 17:27
閱讀 1068·2019-08-29 16:07
閱讀 2961·2019-08-29 13:15
閱讀 1314·2019-08-29 13:14
閱讀 1570·2019-08-26 12:18