亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

跟我學習javascript解決異步編程異常方案

2019-11-20 11:11:49
字體:
來源:轉載
供稿:網友

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

異步I/O、事件驅動使得單線程的JavaScript得以在不阻塞UI的情況下執行網絡、文件訪問功能,且使之在后端實現了較高的性能。然而異步風格也引來了一些麻煩,其中比較核心的問題是:

1、函數嵌套過深

JavaScript的異步調用基于回調函數,當多個異步事務多級依賴時,回調函數會形成多級的嵌套,代碼變成
金字塔型結構。這不僅使得代碼變難看難懂,更使得調試、重構的過程充滿風險。

2、異常處理

回調嵌套不僅僅是使代碼變得雜亂,也使得錯誤處理更復雜。這里主要講講異常處理。

二、異常處理

像很多時髦的語言一樣,JavaScript 也允許拋出異常,隨后再用一個try/catch 語句塊捕獲。如果拋出的異常未被捕獲,大多數JavaScript環境都會提供一個有用的堆棧軌跡。舉個例子,下面這段代碼由于'{'為無效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 行代碼。遺憾的是,自頂向下地跟蹤異步錯誤起源并不都這么直截了當。

異步編程中可能拋出錯誤的情況有兩種:回調函數錯誤、異步函數錯誤。

1、回調函數錯誤

如果從異步回調中拋出錯誤,會發生什么事?讓我們先來做個測試。

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

上述應用的結果是一條極其簡短的堆棧軌跡。

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

等等,A 和B 發生了什么事?為什么它們沒有出現在堆棧軌跡中?這是因為運行C 的時候,異步函數的上下文已經不存在了,A 和B 并不在內存堆棧里。這3 個函數都是從事件隊列直接運行的?;谕瑯拥睦碛?,利用try/catch 語句塊并不能捕獲從異步回調中拋出的錯誤。另外回調函數中的return也失去了意義。

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

看到這里的問題了嗎?這里的try/catch 語句塊只捕獲setTimeout函數自身內部發生的那些錯誤。因為setTimeout 異步地運行其回調,所以即使延時設置為0,回調拋出的錯誤也會直接流向應用程序。

總的來說,取用異步回調的函數即使包裝上try/catch 語句塊,也只是無用之舉。(特例是,該異步函數確實是在同步地做某些事且容易出錯。例如,Node 的fs.watch(file,callback)就是這樣一個函數,它在目標文件不存在時會拋出一個錯誤。)正因為此,Node.js 中的回調幾乎總是接受一個錯誤作為其首個參數,這樣就允許回調自己來決定如何處理這個錯誤。

2、異步函數錯誤

由于異步函數是立刻返回的,異步事務中發生的錯誤是無法通過try-catch來捕捉的,只能采用由調用方提供錯誤處理回調的方案來解決。

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

舉個例子,下面這個Node 應用嘗試異步地讀取一個文件,還負責記錄下任何錯誤(如“文件不存在”)。

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

客戶端JavaScript 庫的一致性要稍微差些,不過最常見的模式是,針對成敗這兩種情形各規定一個單獨的回調。jQuery 的Ajax 方法就遵循了這個模式。

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

不管API 形態像什么,始終要記住的是,只能在回調內部處理源于回調的異步錯誤。

三、未捕獲異常的處理

如果是從回調中拋出異常的,則由那個調用了回調的人負責捕獲該異常。但如果異常從未被捕獲,又會怎么樣?這時,不同的JavaScript環境有著不同的游戲規則……

1. 在瀏覽器環境中

現代瀏覽器會在開發人員控制臺顯示那些未捕獲的異常,接著返回事件隊列。要想修改這種行為,可以給window.onerror 附加一個處理器。如果windows.onerror 處理器返回true,則能阻止瀏覽器的默認錯誤處理行為。

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

在成品應用中, 會考慮某種JavaScript 錯誤處理服務, 譬如Errorception。Errorception 提供了一個現成的windows.onerror 處理器,它向應用服務器報告所有未捕獲的異常,接著應用服務器發送消息通知我們。

2. 在Node.js 環境中

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

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

但是,自Node 0.8.4 起,uncaughtException 事件就被廢棄了。據其文檔所言,對異常處理而言,uncaughtException 是一種非常粗暴的機制,請勿使用uncaughtException,而應使用Domain 對象。

Domain 對象又是什么?你可能會這樣問。Domain 對象是事件化對象,它將throw 轉化為'error'事件。下面是一個例子。

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 只是簡單地觸發了Domain 對象的錯誤處理器。

Error ignored!

很奇妙,是不是?Domain 對象讓throw 語句生動了很多。不管在瀏覽器端還是服務器端,全局的異常處理器都應被視作最后一根救命稻草。請僅在調試時才使用它。

四、幾種解決方案

下面對幾種解決方案的討論主要集中于上面提到的兩個核心問題上,當然也會考慮其他方面的因素來評判其優缺點。

1、Async.js

首先是Node中非常著名的Async.js,這個庫能夠在Node中展露頭角,恐怕也得歸功于Node統一的錯誤處理約定。
而在前端,一開始并沒有形成這么統一的約定,因此使用Async.js的話可能需要對現有的庫進行封裝。

Async.js的其實就是給回調函數的幾種常見使用模式加了一層包裝。比如我們需要三個前后依賴的異步操作,采用純回調函數寫法如下:

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

可以看到,回調函數由原來的橫向發展轉變為縱向發展,同時錯誤被統一傳遞到最后的處理函數中。
其原理是,將函數數組中的后一個函數包裝后作為前一個函數的末參數cb傳入,同時要求:

每一個函數都應當執行其cb參數;cb的第一個參數用來傳遞錯誤。我們可以自己寫一個async.waterfall的實現:

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等多種流程控制方法,來實現常見的異步協作。

Async.js的問題:

在外在上依然沒有擺脫回調函數,只是將其從橫向發展變為縱向,還是需要程序員熟練異步回調風格。
錯誤處理上仍然沒有利用上try-catch和throw,依賴于“回調函數的第一個參數用來傳遞錯誤”這樣的一個約定。

2、Promise方案

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

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

這個函數可以把符合下述規則的異步函數轉換為返回Promise的函數:

回調函數的第一個參數用于傳遞錯誤,第二個參數用于傳遞正常的結果。接著就可以進行操作了:

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,原來明顯的異步回調函數風格顯得更像同步編程風格,我們只需要使用then方法將結果傳遞下去即可,同時return也有了相應的意義:
在每一個then的onFullfilled函數(以及onRejected)里的return,都會為下一個then的onFullfilled函數(以及onRejected)的參數設定好值。

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

3、Generator方案

ES6引入的Generator可以理解為可在運行中轉移控制權給其他代碼,并在需要的時候返回繼續執行的函數。利用Generator可以實現協程的功能。

將Generator與Promise結合,可以進一步將異步代碼轉化為同步風格:

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

然而我們還需要一個可以自動運行Generator的函數:

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

用這個函數來調用Generator即可:

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

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

類似的功能有co/task.js等庫實現。

4、ES7的async/await

ES7中將會引入async function和await關鍵字,利用這個功能,我們可以輕松寫出同步風格的代碼,
同時依然可以利用原有的異步I/O機制。

采用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方案看起來沒有太大區別,只是關鍵字換了換。
實際上async function就是對Generator方案的一個官方認可,將之作為語言內置功能。

async function的缺點:

await只能在async function內部使用,因此一旦你寫了幾個async function,或者使用了依賴于async function的庫,那你很可能會需要更多的async function。

目前處于提案階段的async function還沒有得到任何瀏覽器或Node.JS/io.js的支持。Babel轉碼器也需要打開實驗選項,并且對于不支持Generator的瀏覽器來說,還需要引進一層厚厚的regenerator runtime,想在前端生產環境得到應用還需要時間。

以上就是本文的全部內容,希望對大家的學習有所幫助。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
美日韩在线视频| 国产精品一区二区久久久久| 欧美成人在线免费视频| 国产精品高清网站| 精品久久久久久久久久久久久| 欧美一二三视频| 草民午夜欧美限制a级福利片| 国产精品美女免费看| 成人激情视频小说免费下载| 热久久这里只有精品| 久久成人人人人精品欧| 96sao精品视频在线观看| 中文字幕欧美国内| 国产精品视频地址| 九九热精品视频在线播放| 久久九九精品99国产精品| 国产精品久久久久77777| 最新国产精品拍自在线播放| 亚洲国产精品va在线观看黑人| 亚洲一区二区少妇| 疯狂蹂躏欧美一区二区精品| 国内精品一区二区三区四区| 国产亚洲一级高清| 日本精品中文字幕| 亚洲自拍偷拍一区| 久久久久久久久国产精品| 日韩一区二区三区国产| 欧美又大又粗又长| 一本大道久久加勒比香蕉| 国产精品一区久久| 91精品国产高清自在线看超| 色综合亚洲精品激情狠狠| 精品久久久久久久久久久久| 久久天天躁夜夜躁狠狠躁2022| 日韩在线观看免费全集电视剧网站| 成人午夜小视频| 视频一区视频二区国产精品| 中国china体内裑精亚洲片| 91在线观看欧美日韩| 国产成人精品一区二区三区| 国产精品电影观看| 91成人国产在线观看| 久久中文字幕视频| 国产精品扒开腿做爽爽爽视频| 91在线观看免费高清| 欧美成人精品三级在线观看| 欧美激情在线有限公司| 夜夜嗨av一区二区三区免费区| 日韩在线视频播放| 午夜免费久久久久| 精品福利樱桃av导航| 国产精品免费福利| 成人黄色免费看| 中文字幕在线看视频国产欧美| 欧美日韩在线视频观看| 91在线观看免费观看| 久久国产精品免费视频| 国产欧美精品一区二区三区介绍| 精品国产91久久久| 久久国内精品一国内精品| 久久久久久久国产精品视频| 久久久爽爽爽美女图片| 91精品综合视频| 欧美在线视频网| 国产精品偷伦视频免费观看国产| 最近日韩中文字幕中文| 久久乐国产精品| 久久精品久久久久久| 在线成人免费网站| 4p变态网欧美系列| 亚洲天堂精品在线| 国产成人精品电影| 91高清视频免费| 亚洲国产高潮在线观看| 精品国产欧美成人夜夜嗨| 欧美另类第一页| 亚洲97在线观看| 国产主播欧美精品| 久久久97精品| 久久av红桃一区二区小说| 秋霞成人午夜鲁丝一区二区三区| 国产一区二区三区四区福利| 亚洲人成网在线播放| 久久久久久噜噜噜久久久精品| 午夜免费日韩视频| 尤物99国产成人精品视频| 精品网站999www| 国内精品模特av私拍在线观看| 欧美韩国理论所午夜片917电影| 亚洲人成77777在线观看网| 成人有码在线视频| 亚洲精品v天堂中文字幕| 日韩久久精品成人| 国产v综合ⅴ日韩v欧美大片| 成人国内精品久久久久一区| 日韩电影中文字幕av| 亚洲3p在线观看| 日韩在线精品一区| 国产成人av在线播放| 日韩电影视频免费| 国产精品69久久| 国产精品自拍偷拍视频| 久久综合伊人77777| 国产精品成人观看视频国产奇米| 欧美日本国产在线| 亚洲一区二区三区在线视频| 亚洲人成网站777色婷婷| 亚洲欧美一区二区三区久久| 亚洲香蕉av在线一区二区三区| 91沈先生作品| 亚洲国产精品va在看黑人| 国产精品视频网址| 亚洲国产精品热久久| 日韩电影大全免费观看2023年上| 欧美日韩在线第一页| 青青久久aⅴ北条麻妃| 精品久久久久久久久中文字幕| 久久免费高清视频| 91久久综合亚洲鲁鲁五月天| 6080yy精品一区二区三区| 91免费视频国产| 欧美中文在线观看国产| 精品国产91久久久久久老师| 国产精品大陆在线观看| 成人久久一区二区三区| 国产日韩精品在线| 国产精品99久久久久久www| 亚洲国产成人在线视频| 亚洲男人第一网站| 精品av在线播放| 久久久久久网站| 欧美一级淫片丝袜脚交| 日韩av在线直播| 中文字幕精品久久久久| 精品国内产的精品视频在线观看| 欧美国产在线电影| 136fldh精品导航福利| 欧美午夜影院在线视频| 91国内免费在线视频| 欧美巨猛xxxx猛交黑人97人| 久久精品视频在线| 久久久久久91| 日韩中文在线中文网三级| 97人洗澡人人免费公开视频碰碰碰| 欧美精品免费在线观看| 亚洲区一区二区| 日韩精品在线观看视频| 福利一区视频在线观看| 日韩成人在线免费观看| 国产精品成人免费电影| 亚洲天堂男人天堂女人天堂| 国产精品一区二区性色av| 日本成人在线视频网址| 亚洲乱码一区av黑人高潮| 久久成人一区二区| 欧美夫妻性视频| 亚洲国产精品美女| 国产丝袜一区二区| 98午夜经典影视| 欧美日韩亚洲天堂| 亚洲高清av在线| 最近2019中文字幕在线高清| 综合欧美国产视频二区| 欧美在线视频在线播放完整版免费观看|