多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內(nèi)最全I(xiàn)T社區(qū)平臺 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁 > web前端 > htmlcss > JavaScript學(xué)習(xí)--Item27 異步編程異常解決方案

JavaScript學(xué)習(xí)--Item27 異步編程異常解決方案

來源:程序員人生   發(fā)布時間:2016-06-25 15:49:32 閱讀次數(shù):4239次

1、JavaScript異步編程的兩個核心難點

異步I/O、事件驅(qū)動使得單線程的JavaScript得以在不阻塞UI的情況下履行網(wǎng)絡(luò)、文件訪問功能,且使之在后端實現(xiàn)了較高的性能。但是異步風(fēng)格也引來了1些麻煩,其中比較核心的問題是:

1、函數(shù)嵌套過深

JavaScript的異步調(diào)用基于回調(diào)函數(shù),當(dāng)多個異步事務(wù)多級依賴時,回調(diào)函數(shù)會構(gòu)成多級的嵌套,代碼變成
金字塔型結(jié)構(gòu)。這不但使得代碼變難看難懂,更使得調(diào)試、重構(gòu)的進(jìn)程充滿風(fēng)險。

2、異常處理

回調(diào)嵌套不單單是使代碼變得雜亂,也使得毛病處理更復(fù)雜。這里主要講講異常處理。

2、異常處理

像很多時興的語言1樣,JavaScript 也允許拋出異常,隨后再用1個try/catch 語句塊捕獲。如果拋出的異常未被捕獲,大多數(shù)JavaScript環(huán)境都會提供1個有用的堆棧軌跡。舉個例子,下面這段代碼由于'{'為無效JSON 對象而拋出異常。

function JSONToObject(jsonStr) { return JSON.parse(jsonStr); } var obj = JSONToObject('{'); //SyntaxError: Unexpected end of input //at Object.parse (native) //at JSONToObject (/AsyncJS/stackTrace.js:2:15) //at Object.<anonymous> (/AsyncJS/stackTrace.js:4:11)

堆棧軌跡不但告知我們哪里拋出了毛病,而且說明了最初出錯的地方:第4 行代碼。遺憾的是,自頂向下地跟蹤異步毛病起源其實不都這么直接了當(dāng)。

異步編程中可能拋出毛病的情況有兩種:回調(diào)函數(shù)毛病、異步函數(shù)毛病。

1、回調(diào)函數(shù)毛病

如果從異步回調(diào)中拋出毛病,會產(chǎn)生甚么事?讓我們先來做個測試。

setTimeout(function A() { setTimeout(function B() { setTimeout(function C() { throw new Error('Something terrible has happened!'); }, 0); }, 0); }, 0);

上述利用的結(jié)果是1條極為簡短的堆棧軌跡。

Error: Something terrible has happened! at Timer.C (/AsyncJS/nestedErrors.js:4:13)

等等,A 和B 產(chǎn)生了甚么事?為何它們沒有出現(xiàn)在堆棧軌跡中?這是由于運(yùn)行C 的時候,異步函數(shù)的上下文已不存在了,A 和B 其實不在內(nèi)存堆棧里。這3 個函數(shù)都是從事件隊列直接運(yùn)行的。基于一樣的理由,利用try/catch 語句塊其實不能捕獲從異步回調(diào)中拋出的毛病。另外回調(diào)函數(shù)中的return也失去了意義。

try { setTimeout(function() { throw new Error('Catch me if you can!'); }, 0); } catch (e) { console.error(e); }

看到這里的問題了嗎?這里的try/catch 語句塊只捕獲setTimeout函數(shù)本身內(nèi)部產(chǎn)生的那些毛病。由于setTimeout 異步地運(yùn)行其回調(diào),所以即便延時設(shè)置為0,回調(diào)拋出的毛病也會直接流向利用程序。

總的來講,取用異步回調(diào)的函數(shù)即便包裝上try/catch 語句塊,也只是無用之舉。(特例是,該異步函數(shù)確切是在同步地做某些事且容易出錯。例如,Node 的fs.watch(file,callback)就是這樣1個函數(shù),它在目標(biāo)文件不存在時會拋出1個毛病。)正由于此,Node.js 中的回調(diào)幾近總是接受1個毛病作為其首個參數(shù),這樣就允許回調(diào)自己來決定如何處理這個毛病。

2、異步函數(shù)毛病

由于異步函數(shù)是立刻返回的,異步事務(wù)中產(chǎn)生的毛病是沒法通過try-catch來捕捉的,只能采取由調(diào)用方提供毛病處理回調(diào)的方案來解決。

例如Node中常見的function (err, ...) {...}回調(diào)函數(shù),就是Node中處理毛病的約定:行將毛病作為回調(diào)函數(shù)的第1個實參返回。再比如HTML5中FileReader對象的onerror函數(shù),會被用于處理異步讀取文件進(jìn)程中的毛病。

舉個例子,下面這個Node 利用嘗試異步地讀取1個文件,還負(fù)責(zé)記錄下任何毛病(如“文件不存在”)。

var fs = require('fs'); fs.readFile('fhgwgdz.txt', function(err, data) { if (err) { return console.error(err); }; console.log(data.toString('utf8')); });

客戶端JavaScript 庫的1致性要略微差些,不過最多見的模式是,針對成敗這兩種情形各規(guī)定1個單獨的回調(diào)。jQuery 的Ajax 方法就遵守了這個模式。

$.get('/data', { success: successHandler, failure: failureHandler });

不管API 形態(tài)像甚么,始終要記住的是,只能在回調(diào)內(nèi)部處理源于回調(diào)的異步毛病。

3、未捕獲異常的處理

如果是從回調(diào)中拋出異常的,則由那個調(diào)用了回調(diào)的人負(fù)責(zé)捕獲該異常。但如果異常從未被捕獲,又會怎樣樣?這時候,不同的JavaScript環(huán)境有著不同的游戲規(guī)則……

1. 在閱讀器環(huán)境中

現(xiàn)代閱讀器會在開發(fā)人員控制臺顯示那些未捕獲的異常,接著返回事件隊列。要想修改這類行動,可以給window.onerror 附加1個處理器。如果windows.onerror 處理器返回true,則能禁止閱讀器的默許毛病處理行動。

window.onerror = function(err) { return true; //完全疏忽所有毛病 };

在成品利用中, 會斟酌某種JavaScript 毛病處理服務(wù), 比方Errorception。Errorception 提供了1個現(xiàn)成的windows.onerror 處理器,它向利用服務(wù)器報告所有未捕獲的異常,接著利用服務(wù)器發(fā)送消息通知我們。

2. 在Node.js 環(huán)境中

在Node 環(huán)境中,window.onerror 的類似物就是process 對象的uncaughtException 事件。正常情況下,Node 利用會因未捕獲的異常而立即退出。但只要最少還有1個uncaughtException 事件處理
器,Node 利用就會直接返回事件隊列。

process.on('uncaughtException', function(err) { console.error(err); //避免了關(guān)停的命運(yùn)! });

但是,自Node 0.8.4 起,uncaughtException 事件就被廢棄了。據(jù)其文檔所言,對異常處理而言,uncaughtException 是1種非常粗魯?shù)臋C(jī)制,請勿使用uncaughtException,而應(yīng)使用Domain 對象。

Domain 對象又是甚么?你可能會這樣問。Domain 對象是事件化對象,它將throw 轉(zhuǎn)化為'error'事件。下面是1個例子。

var myDomain = require('domain').create(); myDomain.run(function() { setTimeout(function() { throw new Error('Listen to me!') }, 50); }); myDomain.on('error', function(err) { console.log('Error ignored!'); });

源于延時事件的throw 只是簡單地觸發(fā)了Domain 對象的毛病處理器。

Error ignored!

很奇妙,是否是?Domain 對象讓throw 語句生動了很多。不管在閱讀器端還是服務(wù)器端,全局的異常處理器都應(yīng)被視作最后1根救命稻草。請僅在調(diào)試時才使用它。

4、幾種解決方案

下面對幾種解決方案的討論主要集中于上面提到的兩個核心問題上,固然也會斟酌其他方面的因夙來評判其優(yōu)缺點。

1、Async.js

首先是Node中非常著名的Async.js,這個庫能夠在Node中展露頭角,恐怕也得歸功于Node統(tǒng)1的毛病處理約定。
而在前端,1開始并沒有構(gòu)成這么統(tǒng)1的約定,因此使用Async.js的話可能需要對現(xiàn)有的庫進(jìn)行封裝。

Async.js的其實就是給回調(diào)函數(shù)的幾種常見使用模式加了1層包裝。比如我們需要3個前后依賴的異步操作,采取純回調(diào)函數(shù)寫法以下:

asyncOpA(a, b, (err, result) => { if (err) { handleErrorA(err); } asyncOpB(c, result, (err, result) => { if (err) { handleErrorB(err); } asyncOpB(d, result, (err, result) => { if (err) { handlerErrorC(err); } finalOp(result); }); }); });

如果我們采取async庫來做:

async.waterfall([ (cb) => { asyncOpA(a, b, (err, result) => { cb(err, c, result); }); }, (c, lastResult, cb) => { asyncOpB(c, lastResult, (err, result) => { cb(err, d, result); }) }, (d, lastResult, cb) => { asyncOpC(d, lastResult, (err, result) => { cb(err, result); }); } ], (err, finalResult) => { if (err) { handlerError(err); } finalOp(finalResult); });

可以看到,回調(diào)函數(shù)由原來的橫向發(fā)輾轉(zhuǎn)變成縱向發(fā)展,同時毛病被統(tǒng)1傳遞到最后的處理函數(shù)中。
其原理是,將函數(shù)數(shù)組中的后1個函數(shù)包裝后作為前1個函數(shù)的末參數(shù)cb傳入,同時要求:

每個函數(shù)都應(yīng)當(dāng)履行其cb參數(shù);cb的第1個參數(shù)用來傳遞毛病。我們可以自己寫1個async.waterfall的實現(xiàn):

let async = { waterfall: (methods, finalCb = _emptyFunction) => { if (!_isArray(methods)) { return finalCb(new Error('First argument to waterfall must be an array of functions')); } if (!methods.length) { return finalCb(); } function wrap(n) { if (n === methods.length) { return finalCb; } return function (err, ...args) { if (err) { return finalCb(err); } methods[n](...args, wrap(n + 1)); } } wrap(0)(false); } };

Async.js還有series/parallel/whilst等多種流程控制方法,來實現(xiàn)常見的異步協(xié)作。

Async.js的問題:

在外在上仍然沒有擺脫回調(diào)函數(shù),只是將其從橫向發(fā)展變成縱向,還是需要程序員熟練異步回調(diào)風(fēng)格。
毛病處理上依然沒有益用上try-catch和throw,依賴于“回調(diào)函數(shù)的第1個參數(shù)用來傳遞毛病”這樣的1個約定。

2、Promise方案

ES6的Promise來源于Promise/A+。使用Promise來進(jìn)行異步流程控制,有幾個需要注意的問題,
把前面提到的功能用Promise來實現(xiàn),需要先包裝異步函數(shù),使之能返回1個Promise:

function toPromiseStyle(fn) { return (...args) => { return new Promise((resolve, reject) => { fn(...args, (err, result) => { if (err) reject(err); resolve(result); }) }); }; }

這個函數(shù)可以把符合下述規(guī)則的異步函數(shù)轉(zhuǎn)換為返回Promise的函數(shù):

回調(diào)函數(shù)的第1個參數(shù)用于傳遞毛病,第2個參數(shù)用于傳遞正常的結(jié)果。接著就能夠進(jìn)行操作了:

let [opA, opB, opC] = [asyncOpA, asyncOpB, asyncOpC].map((fn) => toPromiseStyle(fn)); opA(a, b) .then((res) => { return opB(c, res); }) .then((res) => { return opC(d, res); }) .then((res) => { return finalOp(res); }) .catch((err) => { handleError(err); });

通過Promise,原來明顯的異步回調(diào)函數(shù)風(fēng)格顯得更像同步編程風(fēng)格,我們只需要使用then方法將結(jié)果傳遞下去便可,同時return也有了相應(yīng)的意義:
在每個then的onFullfilled函數(shù)(和onRejected)里的return,都會為下1個then的onFullfilled函數(shù)(和onRejected)的參數(shù)設(shè)定好值。

如此1來,return、try-catch/throw都可使用了,但catch是以方法的情勢出現(xiàn),還是不盡如人意。

3、Generator方案

ES6引入的Generator可以理解為可在運(yùn)行中轉(zhuǎn)移控制權(quán)給其他代碼,并在需要的時候返回繼續(xù)履行的函數(shù)。利用Generator可以實現(xiàn)協(xié)程的功能。

將Generator與Promise結(jié)合,可以進(jìn)1步將異步代碼轉(zhuǎn)化為同步風(fēng)格:

function* getResult() { let res, a, b, c, d; try { res = yield opA(a, b); res = yield opB(c, res); res = yield opC(d); return res; } catch (err) { return handleError(err); } }

但是我們還需要1個可以自動運(yùn)行Generator的函數(shù):

function spawn(genF, ...args) { return new Promise((resolve, reject) => { let gen = genF(...args); function next(fn) { try { let r = fn(); if (r.done) { resolve(r.value); } Promise.resolve(r.value) .then((v) => { next(() => { return gen.next(v); }); }).catch((err) => { next(() => { return gen.throw(err); }) }); } catch (err) { reject(err); } } next(() => { return gen.next(undefined); }); }); }

用這個函數(shù)來調(diào)用Generator便可:

spawn(getResult) .then((res) => { finalOp(res); }) .catch((err) => { handleFinalOpError(err); });

可見try-catch和return實際上已以其本來面貌回到了代碼中,在代碼情勢上也已看不到異步風(fēng)格的痕跡。

類似的功能有co/task.js等庫實現(xiàn)。

4、ES7的async/await

ES7中將會引入async function和await關(guān)鍵字,利用這個功能,我們可以輕松寫出同步風(fēng)格的代碼,
同時仍然可以利用原本的異步I/O機(jī)制。

采取async function,我們可以將之前的代碼寫成這樣:

async function getResult() { let res, a, b, c, d; try { res = await opA(a, b); res = await opB(c, res); res = await opC(d); return res; } catch (err) { return handleError(err); } } getResult();

和Generator & Promise方案看起來沒有太大區(qū)分,只是關(guān)鍵字換了換。
實際上async function就是對Generator方案的1個官方認(rèn)可,將之作為語言內(nèi)置功能。

async function的缺點:

await只能在async function內(nèi)部使用,因此1旦你寫了幾個async function,或使用了依賴于async function的庫,那你極可能會需要更多的async function。

目前處于提案階段的async function還沒有得到任何閱讀器或Node.JS/io.js的支持。Babel轉(zhuǎn)碼器也需要打開實驗選項,并且對不支持Generator的閱讀器來講,還需要引進(jìn)1層厚厚的regenerator runtime,想在前端生產(chǎn)環(huán)境得到利用還需要時間。

參考:

  • JavaScript異步編程解決方案筆記

  • 《JavaScript異步編程》:有點僵硬不過應(yīng)當(dāng)準(zhǔn)確的JS異步手冊

生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 国产全黄一级毛片 | 精品视频一区二区三区在线观看 | 国产午夜亚洲精品久久www | 亚洲成a人片在线观看中文动漫 | 日本无卡αv免费视频 | 456亚洲人成影院在线观 | 久久久无码精品亚洲日韩按摩 | 性欧美高清| 久久性妇女精品免费 | 最近更新中文字幕3 | 欧美日韩国产亚洲综合不卡 | 精品看片| 99伊人精品 | 国产精品国产三级国产 | 久久丝袜精品综合网站 | 中文字幕二区 | 国产成人免费不卡在线观看 | 精品国产精品a | 国产乱码亚洲精品一区二区 | 精品国产欧美精品v | 爱爱网网站免费观看 | 91大片| 日韩精品亚洲人成在线观看 | 国产美女主播一级成人毛片 | 在线免费午夜视频 | 国产精品成aⅴ人片在线观看 | 欧美成人h版影片在线观看 欧美成人h版影院在线播放 | 在线视频黄 | 最近最全中文字幕 | 亚洲午夜a | 交在线观看网站视频 | 日本一道dvd在线中文字幕 | 亚洲精品综合一二三区在线 | 精品欧美一区二区三区精品久久 | 最近更新中文字幕4 | 嫩草影院在线观看精品视频 | 亚洲成a人片在线v观看 | α毛片 | 又粗又硬又黄又爽的免费视频 | 免费视频www| jizz日本视频 |