欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

跟我學(xué)習(xí)javascript解決異步編程異常方案

 更新時(shí)間:2015年11月23日 14:28:13   作者:小平果118  
跟我學(xué)習(xí)javascript解決異步編程異常方案,感興趣的小伙伴們可以參考一下

一、JavaScript異步編程的兩個(gè)核心難點(diǎn)

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

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

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

2、異常處理

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

二、異常處理

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

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)

堆棧軌跡不僅告訴我們哪里拋出了錯(cuò)誤,而且說明了最初出錯(cuò)的地方:第4 行代碼。遺憾的是,自頂向下地跟蹤異步錯(cuò)誤起源并不都這么直截了當(dāng)。

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

1、回調(diào)函數(shù)錯(cuò)誤

如果從異步回調(diào)中拋出錯(cuò)誤,會(huì)發(fā)生什么事?讓我們先來(lái)做個(gè)測(cè)試。

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

上述應(yīng)用的結(jié)果是一條極其簡(jiǎn)短的堆棧軌跡。

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

等等,A 和B 發(fā)生了什么事?為什么它們沒有出現(xiàn)在堆棧軌跡中?這是因?yàn)檫\(yùn)行C 的時(shí)候,異步函數(shù)的上下文已經(jīng)不存在了,A 和B 并不在內(nèi)存堆棧里。這3 個(gè)函數(shù)都是從事件隊(duì)列直接運(yùn)行的?;谕瑯拥睦碛桑胻ry/catch 語(yǔ)句塊并不能捕獲從異步回調(diào)中拋出的錯(cuò)誤。另外回調(diào)函數(shù)中的return也失去了意義。

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

看到這里的問題了嗎?這里的try/catch 語(yǔ)句塊只捕獲setTimeout函數(shù)自身內(nèi)部發(fā)生的那些錯(cuò)誤。因?yàn)閟etTimeout 異步地運(yùn)行其回調(diào),所以即使延時(shí)設(shè)置為0,回調(diào)拋出的錯(cuò)誤也會(huì)直接流向應(yīng)用程序。

總的來(lái)說,取用異步回調(diào)的函數(shù)即使包裝上try/catch 語(yǔ)句塊,也只是無(wú)用之舉。(特例是,該異步函數(shù)確實(shí)是在同步地做某些事且容易出錯(cuò)。例如,Node 的fs.watch(file,callback)就是這樣一個(gè)函數(shù),它在目標(biāo)文件不存在時(shí)會(huì)拋出一個(gè)錯(cuò)誤。)正因?yàn)榇?,Node.js 中的回調(diào)幾乎總是接受一個(gè)錯(cuò)誤作為其首個(gè)參數(shù),這樣就允許回調(diào)自己來(lái)決定如何處理這個(gè)錯(cuò)誤。

2、異步函數(shù)錯(cuò)誤

由于異步函數(shù)是立刻返回的,異步事務(wù)中發(fā)生的錯(cuò)誤是無(wú)法通過try-catch來(lái)捕捉的,只能采用由調(diào)用方提供錯(cuò)誤處理回調(diào)的方案來(lái)解決。

例如Node中常見的function (err, ...) {...}回調(diào)函數(shù),就是Node中處理錯(cuò)誤的約定:即將錯(cuò)誤作為回調(diào)函數(shù)的第一個(gè)實(shí)參返回。再比如HTML5中FileReader對(duì)象的onerror函數(shù),會(huì)被用于處理異步讀取文件過程中的錯(cuò)誤。

舉個(gè)例子,下面這個(gè)Node 應(yīng)用嘗試異步地讀取一個(gè)文件,還負(fù)責(zé)記錄下任何錯(cuò)誤(如“文件不存在”)。

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

客戶端JavaScript 庫(kù)的一致性要稍微差些,不過最常見的模式是,針對(duì)成敗這兩種情形各規(guī)定一個(gè)單獨(dú)的回調(diào)。jQuery 的Ajax 方法就遵循了這個(gè)模式。

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

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

三、未捕獲異常的處理

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

1. 在瀏覽器環(huán)境中

現(xiàn)代瀏覽器會(huì)在開發(fā)人員控制臺(tái)顯示那些未捕獲的異常,接著返回事件隊(duì)列。要想修改這種行為,可以給window.onerror 附加一個(gè)處理器。如果windows.onerror 處理器返回true,則能阻止瀏覽器的默認(rèn)錯(cuò)誤處理行為。

window.onerror = function(err) {
 return true; //徹底忽略所有錯(cuò)誤
};

在成品應(yīng)用中, 會(huì)考慮某種JavaScript 錯(cuò)誤處理服務(wù), 譬如Errorception。Errorception 提供了一個(gè)現(xiàn)成的windows.onerror 處理器,它向應(yīng)用服務(wù)器報(bào)告所有未捕獲的異常,接著應(yīng)用服務(wù)器發(fā)送消息通知我們。

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

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

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

但是,自Node 0.8.4 起,uncaughtException 事件就被廢棄了。據(jù)其文檔所言,對(duì)異常處理而言,uncaughtException 是一種非常粗暴的機(jī)制,請(qǐng)勿使用uncaughtException,而應(yīng)使用Domain 對(duì)象。

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

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!');
});

源于延時(shí)事件的throw 只是簡(jiǎn)單地觸發(fā)了Domain 對(duì)象的錯(cuò)誤處理器。

Error ignored!

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

四、幾種解決方案

下面對(duì)幾種解決方案的討論主要集中于上面提到的兩個(gè)核心問題上,當(dāng)然也會(huì)考慮其他方面的因素來(lái)評(píng)判其優(yōu)缺點(diǎn)。

1、Async.js

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

Async.js的其實(shí)就是給回調(diào)函數(shù)的幾種常見使用模式加了一層包裝。比如我們需要三個(gè)前后依賴的異步操作,采用純回調(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庫(kù)來(lái)做:

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ù)由原來(lái)的橫向發(fā)展轉(zhuǎn)變?yōu)榭v向發(fā)展,同時(shí)錯(cuò)誤被統(tǒng)一傳遞到最后的處理函數(shù)中。
其原理是,將函數(shù)數(shù)組中的后一個(gè)函數(shù)包裝后作為前一個(gè)函數(shù)的末參數(shù)cb傳入,同時(shí)要求:

每一個(gè)函數(shù)都應(yīng)當(dāng)執(zhí)行其cb參數(shù);cb的第一個(gè)參數(shù)用來(lái)傳遞錯(cuò)誤。我們可以自己寫一個(gè)async.waterfall的實(shí)現(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等多種流程控制方法,來(lái)實(shí)現(xiàn)常見的異步協(xié)作。

Async.js的問題:

在外在上依然沒有擺脫回調(diào)函數(shù),只是將其從橫向發(fā)展變?yōu)榭v向,還是需要程序員熟練異步回調(diào)風(fēng)格。
錯(cuò)誤處理上仍然沒有利用上try-catch和throw,依賴于“回調(diào)函數(shù)的第一個(gè)參數(shù)用來(lái)傳遞錯(cuò)誤”這樣的一個(gè)約定。

2、Promise方案

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

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

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

回調(diào)函數(shù)的第一個(gè)參數(shù)用于傳遞錯(cuò)誤,第二個(gè)參數(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,原來(lái)明顯的異步回調(diào)函數(shù)風(fēng)格顯得更像同步編程風(fēng)格,我們只需要使用then方法將結(jié)果傳遞下去即可,同時(shí)return也有了相應(yīng)的意義:
在每一個(gè)then的onFullfilled函數(shù)(以及onRejected)里的return,都會(huì)為下一個(gè)then的onFullfilled函數(shù)(以及onRejected)的參數(shù)設(shè)定好值。

如此一來(lái),return、try-catch/throw都可以使用了,但catch是以方法的形式出現(xiàn),還是不盡如人意。

3、Generator方案

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

將Generator與Promise結(jié)合,可以進(jìn)一步將異步代碼轉(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);
 }
}

然而我們還需要一個(gè)可以自動(dòng)運(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);
 });
 });
}

用這個(gè)函數(shù)來(lái)調(diào)用Generator即可:

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

可見try-catch和return實(shí)際上已經(jīng)以其原本面貌回到了代碼中,在代碼形式上也已經(jīng)看不到異步風(fēng)格的痕跡。

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

4、ES7的async/await

ES7中將會(huì)引入async function和await關(guān)鍵字,利用這個(gè)功能,我們可以輕松寫出同步風(fēng)格的代碼,
同時(shí)依然可以利用原有的異步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方案看起來(lái)沒有太大區(qū)別,只是關(guān)鍵字換了換。
實(shí)際上async function就是對(duì)Generator方案的一個(gè)官方認(rèn)可,將之作為語(yǔ)言內(nèi)置功能。

async function的缺點(diǎn):

await只能在async function內(nèi)部使用,因此一旦你寫了幾個(gè)async function,或者使用了依賴于async function的庫(kù),那你很可能會(huì)需要更多的async function。

目前處于提案階段的async function還沒有得到任何瀏覽器或Node.JS/io.js的支持。Babel轉(zhuǎn)碼器也需要打開實(shí)驗(yàn)選項(xiàng),并且對(duì)于不支持Generator的瀏覽器來(lái)說,還需要引進(jìn)一層厚厚的regenerator runtime,想在前端生產(chǎn)環(huán)境得到應(yīng)用還需要時(shí)間。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。

相關(guān)文章

最新評(píng)論