数码资讯
跟着我使用Javascript来解决异步编程异常
javascript异步编程的一个、两个核心难点 异步I/O和事件驱动使单线程Javascript能够在不阻塞UI的情况下执行网络和文件访问功能,并在后端实现高性能,然而异步样式也导致了一些问题,而问题的核心是: 1。嵌套函数太深。 Javascript的异步调用基于回调函数。当多个异步事务是多层次
选购提示
关注价格、性能、续航、售后和真实使用场景,理性比较后再下单。
javascript异步编程的一个、两个核心难点
异步I/O和事件驱动使单线程Javascript能够在不阻塞UI的情况下执行网络和文件访问功能,并在后端实现高性能,然而异步样式也导致了一些问题,而问题的核心是:
1。嵌套函数太深。
Javascript的异步调用基于回调函数。当多个异步事务是多层次的时,回调函数将形成多级嵌套,并且代码成为
金字塔型结构,这不仅使代码难以理解,而且使调试和重构的过程充满风险。
2。异常处理
回调嵌套不仅使代码混乱,而且使错误处理更加复杂。
两。异常处理
像很多时髦的语言,Javascript也可以抛出一个异常,然后被一块试试/ catch语句。如果抛出的异常没有被捕获,大多数Javascript环境提供了一个有益的堆栈轨迹。例如,下面的代码是无效的因为{ 'json对象和抛出异常。
功能jsontoobject(jsonstr){
返回JSON.parse(jsonstr);
}
var obj = jsontoobject({);
/ /查询:输入意外结束
/ /在object.parse(母语)
/ /在jsontoobject( / / asyncjs堆栈。JS:2:15)
对象。( / / asyncjs堆栈。JS:4:11)
堆栈跟踪不仅告诉我们错误的位置,而且还告诉我们原始错误的位置:第四行代码。不幸的是,从上到下追踪异步错误的起源并不是那么简单。
异步编程中有两种可能的错误:回调函数错误、异步函数错误。
1。回调函数的错误
如果从异步回调中抛出错误,会发生什么情况让我们先做个测试。
setTimeout(函数(){)
setTimeout(函数B(){)
setTimeout(函数C(){)
把新的错误(可怕的事情发生了!);
},0);
},0);
},0);
上述应用程序的结果是一个非常短的堆栈轨道。
错误:发生了可怕的事情!
在定时器。C( / / nestederrors asyncjs js:4:13)
等等,A和B怎么了为什么他们没有出现在堆栈轨道这是因为当C运行的异步函数中不存在,与A和B都不在内存堆栈。这3个函数都是直接从事件队列。出于同样的原因,在尝试/使用catch语句块不捕捉错误异步回调抛出。此外,在回调函数的返回也就失去了它的意义。
{试
setTimeout(){()函数(
把新的错误('catch我如果你能!;
},0);
} catch(e){
Console.error(E);
}
你看到这里的问题了吗try / catch块捕获这只在setTimeout函数本身所发生的错误。因为setTimeout回调异步运行,即使延迟设置为0,由回调抛出的错误会直接流到应用。
一般来说,异步回调函数是一个无用的举动即使try / catch语句块包裹。(例如,这种异步功能是否同步做某事是容易出错。例如,节点的fs.watch(文件、回调)是一个函数抛出一个错误时,目标文件不存在。)因此,回调在Node.js几乎总是接受错误的作为第一个参数,它允许回调函数来决定如何处理这个错误。
2、异步函数错误
由于异步函数立即返回,异步事务中发生的错误无法通过catch捕获捕获,只能通过调用方的错误处理回调方案来解决。
例如,一个共同的功能(呃,…){…}回调函数在节点是常规处理错误的节点:错误是回来作为回调函数的第一个参数。例如,在HTML5的FileReader对象的误差函数将用于在异步读取文件的过程处理错误。
例如,下面的节点应用程序尝试异步读取文件并记录任何错误(例如不存在文件)。
VaR FS =需要('fs);
fs.readfile('fhgwgdz .txt功能(呃,数据){
如果(错误){
返回console.error(ERR);
};
console.log(data.tostring('utf8 '));
});
客户端Javascript库的一致性稍差,但最常见的模式是为这两种情况中的每一种指定一个回调函数。
获取( 数据){
成功:successhandler,
失败:failurehandler
});
无论API形式是什么,总是要记住来自回调的异步错误只能在回调中处理。
三。未捕获的异常处理
如果回调中抛出异常,调用回调的人员负责捕获异常。但是,如果异常未被捕获,该怎么办呢此时,不同的Javascript环境有不同的游戏规则…
1。在浏览器环境中
现代的浏览器会在开发者控制台显示那些未捕获的异常,然后返回到事件队列。修改此行为,你可以添加一个处理器的windows.onerror处理器返回到真实的window.onerror.if,浏览器的默认错误处理行为是可以预防的。
不检测功能(ERR){
返回true;完全忽略所有错误
};
在成品应用,一些Javascript错误处理服务,如errorception,是考虑。errorception提供了一个现成的windows.onerror处理器,并报告所有未捕获的异常的应用服务器,然后发送通知给我们由服务器发送的消息。
2。在Node.js环境
在节点的环境中,在不检测类似的事情是处理对象的uncaughtException事件。在正常情况下,节点的应用程序将立即退出由于未捕获的异常。但只要有至少一个uncaughtException事件处理
节点应用程序将直接返回事件队列。
process.on('uncaughtexception功能(ERR){
Console.error(ERR); / /避免关停的命运!
});
然而,从节点0.8.4,uncaughtException事件已经被抛弃了。根据其文档,uncaughtException是异常处理的一个非常粗糙的机制。不要使用uncaughtException,但使用的域对象。
什么是域对象你可能会问,域对象是一个事件对象,将投入a'error'事件。下面是一个例子。
VaR MyDomain =需要('domain)创建();
MyDomain.run(function(){()
setTimeout(){()函数(
把新的错误(听著我!!)
},50);
});
MyDomain.on(错误,函数(ERR){
console.log(错误被忽略!;
});
源于延迟事件的抛出只触发域对象的错误处理器。
忽略错误!
太棒了,不是吗域对象使抛出语句变得更加生动,在浏览器和服务器端,全局异常处理程序应被视为最后一根稻草,只有在调试时才使用它。
四,几种解决方案
以下几个解决方案的讨论主要集中在上述两个核心问题上。当然,也会考虑其他因素来评估它们的优缺点。
1、Async.js
首先是在节点将在图书馆Async.js非常著名的节点,可能是由于统一的错误处理协议节点。
在前端,没有统一的会议开始,所以async.js可能需要使用现有的库封装。
事实上,Async.js增加了一层包装,回调函数的几种常见的使用模式。例如,我们需要三个前、后相关的异步操作,包括使用纯的回调函数:
AsyncOpA(A,B,(呃,结果)= > {
如果(错误){
HandleErrorA(ERR);
}
AsyncOpB(C,结果,(呃,结果)= > {
如果(错误){
HandleErrorB(ERR);
}
AsyncOpB(D,结果,(呃,结果)= > {
如果(错误){
HandlerErrorC(ERR);
}
finalop(结果);
});
});
});
如果我们使用异步库来做:
Async.waterfall({
= {(CB)
AsyncOpA(A,B,(呃,结果)= > {
CB(错误,C,结果);
});
},
(C,lastresult,CB)= > {
AsyncOpB(C,lastresult,(呃,结果)= > {
CB(错误,D,结果);
})
},
(d,lastresult,CB)= > {
AsyncOpC(D,lastresult,(呃,结果)= > {
CB(错误,结果);
});
}
},(呃,最终结果)= > {
如果(错误){
HandlerError(ERR);
}
finalop(最终结果);
});
如您所见,回调函数从原来的横向开发转换为垂直开发,并将错误均匀地传递给最终处理函数。
其原理是将函数中的后一个函数封装为前一个函数的最后一个参数CB,并请求:
各职能部门应执行其CB参数;对CB的第一个参数是用来传递错误。我们可以自己写一个async.waterfall实施:
让async = { {
瀑布:(方法,finalcb = _emptyfunction = > {)
如果(!_isarray(方法)){
返回finalcb(新的错误(第一论点瀑布必须是一个数组));
}
如果(!方法(长度){
返回finalcb();
}
函数包装(n){
如果(n =方法长度){
返回finalcb;
}
返回函数(错误),…args){
如果(错误){
返回finalcb(ERR);
}
方法{ }(…一、包装(n + 1));
}
}
包装(0)(假);
}
};
async.js还具有多种过程控制方法等系列 / /平行而共同实现异步协作。
异步js的问题:
它仍然不能摆脱外部的回调函数,只是将它从水平更改为垂直,或者需要程序员熟练的异步回调样式。
仍然没有使用上层catch捕获和抛出错误处理,这取决于一个约定,即回调函数的第一个参数用于传递错误。
2、承诺计划
ES6的承诺是承诺 / +。使用异步过程控制的承诺,还有应注意的几个问题,
为了实现前面提到的函数,我们需要先包装异步函数,这样它就可以返回到一个承诺中:
功能topromisestyle(FN){
收益= > {(参数…)
返回新的承诺((解析,拒绝)= {
FN(args,…(错误,结果)= {
如果(错误)拒绝(错误);
解决(结果);
})
});
};
}
此函数可以将符合以下规则的异步函数转换为返回到希望的函数:
回调函数的第一个参数用于传递错误,第二个参数用于传递正常结果,然后操作即可完成:
让{ OPA,OPB、OPC } = { asyncopa,asyncopb,asyncopc }。地图((FN)= topromisestyle(FN));
零帕(A,B)
然后((=){
返回OPB(C,RES);
})
然后((=){
返回OPC(d,RES);
})
然后((=){
返回finalop(RES);
})
catch((错误)= {
HandleError(ERR);
});
通过承诺,原始异步回调函数样式更像是同步编程风格。我们只需要把结果和当时的方法联系起来,回报也就有了相应的意义。
在每一个随后的onfullfilled功能(和onrejected),还将为下一onfullfilled函数参数的一个很好的价值(和onrejected)然后。
因此,可以使用返回和尝试捕获/抛出,但catch是一种方法的形式,或者不那么令人满意。
3、发电机方案
发电机由6可以理解为将控制传递到其他代码在运行的能力,和返回功能,将继续在需要的时候。在有限范围的功能可以通过使用发生器的实现。
将生成器与承诺相结合,异步代码可以进一步转换为同步样式:
功能* getResult(){
设a,b,c,d;
{试
RES =产量(A,B)OPA;
RES =产量OPB(C,RES);
收率OPC(d);
返回水库;
} catch(错误){
返回handleError(ERR);
}
}
但是,我们还需要一个能自动运行生成器的函数:
功能菌种(日内瓦,…args){
返回新的承诺((解析,拒绝)= {
让创=日内瓦(…args);
函数下一个(FN){
{试
设r=FN();
如果(r.done){
解决(r.value);
}
promise.resolve(r.value)
然后((v)= { {
(下一步)= {
返回gen.next(V);
});
}
(下一步)= {
返回gen.throw(ERR);
})
});
} catch(错误){
拒绝(错误);
}
}
(下一步)= {
返回gen.next(不确定);
});
});
}
使用此函数调用生成器:
产卵(getResult)
然后((=){
finalop(RES);
})
catch((错误)= {
HandleFinalOpError(ERR);
});
可以看出,尝试捕获和返回实际上返回了具有原始功能的代码,并且代码形式没有异步样式的踪迹。
类似的功能与共/ task.js和其他库的实现。
4、异步/等待ES7
ES7将介绍异步功能等关键词,利用这个功能,我们可以轻松的写同步代码,
同时,仍然可以使用原始的异步I/O机制。
异步函数,我们可以写上这样的代码:
异步函数getResult(){
设a,b,c,d;
{试
分辨率=(a,b)等待OPA;
RES =等待OPB(C,RES);
等待OPC(d);
返回水库;
} catch(错误){
返回handleError(ERR);
}
}
GetResult();
它与生成器承诺方案看起来不太一样,但是密钥被更改了。
事实上,异步函数发生器的方案的正式认可,这是作为一个语言内置函数。
异步函数的缺点:
等待仅能在异步功能的使用,所以一旦你写了几个异步功能或使用异步函数库,你可能需要更多的异步函数。
异步功能,这是目前在提案阶段,还没有通过任何浏览器或节点的支持。js / io.js.babel转码也需要开放的实验方案,并对非发电机的浏览器,我们需要引入一个厚的蓄热运行。要使前端生产环境得以应用还需要时间。
以上是本文的全部内容,希望能对大家有所帮助。
异步I/O和事件驱动使单线程Javascript能够在不阻塞UI的情况下执行网络和文件访问功能,并在后端实现高性能,然而异步样式也导致了一些问题,而问题的核心是:
1。嵌套函数太深。
Javascript的异步调用基于回调函数。当多个异步事务是多层次的时,回调函数将形成多级嵌套,并且代码成为
金字塔型结构,这不仅使代码难以理解,而且使调试和重构的过程充满风险。
2。异常处理
回调嵌套不仅使代码混乱,而且使错误处理更加复杂。
两。异常处理
像很多时髦的语言,Javascript也可以抛出一个异常,然后被一块试试/ catch语句。如果抛出的异常没有被捕获,大多数Javascript环境提供了一个有益的堆栈轨迹。例如,下面的代码是无效的因为{ 'json对象和抛出异常。
功能jsontoobject(jsonstr){
返回JSON.parse(jsonstr);
}
var obj = jsontoobject({);
/ /查询:输入意外结束
/ /在object.parse(母语)
/ /在jsontoobject( / / asyncjs堆栈。JS:2:15)
对象。( / / asyncjs堆栈。JS:4:11)
堆栈跟踪不仅告诉我们错误的位置,而且还告诉我们原始错误的位置:第四行代码。不幸的是,从上到下追踪异步错误的起源并不是那么简单。
异步编程中有两种可能的错误:回调函数错误、异步函数错误。
1。回调函数的错误
如果从异步回调中抛出错误,会发生什么情况让我们先做个测试。
setTimeout(函数(){)
setTimeout(函数B(){)
setTimeout(函数C(){)
把新的错误(可怕的事情发生了!);
},0);
},0);
},0);
上述应用程序的结果是一个非常短的堆栈轨道。
错误:发生了可怕的事情!
在定时器。C( / / nestederrors asyncjs js:4:13)
等等,A和B怎么了为什么他们没有出现在堆栈轨道这是因为当C运行的异步函数中不存在,与A和B都不在内存堆栈。这3个函数都是直接从事件队列。出于同样的原因,在尝试/使用catch语句块不捕捉错误异步回调抛出。此外,在回调函数的返回也就失去了它的意义。
{试
setTimeout(){()函数(
把新的错误('catch我如果你能!;
},0);
} catch(e){
Console.error(E);
}
你看到这里的问题了吗try / catch块捕获这只在setTimeout函数本身所发生的错误。因为setTimeout回调异步运行,即使延迟设置为0,由回调抛出的错误会直接流到应用。
一般来说,异步回调函数是一个无用的举动即使try / catch语句块包裹。(例如,这种异步功能是否同步做某事是容易出错。例如,节点的fs.watch(文件、回调)是一个函数抛出一个错误时,目标文件不存在。)因此,回调在Node.js几乎总是接受错误的作为第一个参数,它允许回调函数来决定如何处理这个错误。
2、异步函数错误
由于异步函数立即返回,异步事务中发生的错误无法通过catch捕获捕获,只能通过调用方的错误处理回调方案来解决。
例如,一个共同的功能(呃,…){…}回调函数在节点是常规处理错误的节点:错误是回来作为回调函数的第一个参数。例如,在HTML5的FileReader对象的误差函数将用于在异步读取文件的过程处理错误。
例如,下面的节点应用程序尝试异步读取文件并记录任何错误(例如不存在文件)。
VaR FS =需要('fs);
fs.readfile('fhgwgdz .txt功能(呃,数据){
如果(错误){
返回console.error(ERR);
};
console.log(data.tostring('utf8 '));
});
客户端Javascript库的一致性稍差,但最常见的模式是为这两种情况中的每一种指定一个回调函数。
获取( 数据){
成功:successhandler,
失败:failurehandler
});
无论API形式是什么,总是要记住来自回调的异步错误只能在回调中处理。
三。未捕获的异常处理
如果回调中抛出异常,调用回调的人员负责捕获异常。但是,如果异常未被捕获,该怎么办呢此时,不同的Javascript环境有不同的游戏规则…
1。在浏览器环境中
现代的浏览器会在开发者控制台显示那些未捕获的异常,然后返回到事件队列。修改此行为,你可以添加一个处理器的windows.onerror处理器返回到真实的window.onerror.if,浏览器的默认错误处理行为是可以预防的。
不检测功能(ERR){
返回true;完全忽略所有错误
};
在成品应用,一些Javascript错误处理服务,如errorception,是考虑。errorception提供了一个现成的windows.onerror处理器,并报告所有未捕获的异常的应用服务器,然后发送通知给我们由服务器发送的消息。
2。在Node.js环境
在节点的环境中,在不检测类似的事情是处理对象的uncaughtException事件。在正常情况下,节点的应用程序将立即退出由于未捕获的异常。但只要有至少一个uncaughtException事件处理
节点应用程序将直接返回事件队列。
process.on('uncaughtexception功能(ERR){
Console.error(ERR); / /避免关停的命运!
});
然而,从节点0.8.4,uncaughtException事件已经被抛弃了。根据其文档,uncaughtException是异常处理的一个非常粗糙的机制。不要使用uncaughtException,但使用的域对象。
什么是域对象你可能会问,域对象是一个事件对象,将投入a'error'事件。下面是一个例子。
VaR MyDomain =需要('domain)创建();
MyDomain.run(function(){()
setTimeout(){()函数(
把新的错误(听著我!!)
},50);
});
MyDomain.on(错误,函数(ERR){
console.log(错误被忽略!;
});
源于延迟事件的抛出只触发域对象的错误处理器。
忽略错误!
太棒了,不是吗域对象使抛出语句变得更加生动,在浏览器和服务器端,全局异常处理程序应被视为最后一根稻草,只有在调试时才使用它。
四,几种解决方案
以下几个解决方案的讨论主要集中在上述两个核心问题上。当然,也会考虑其他因素来评估它们的优缺点。
1、Async.js
首先是在节点将在图书馆Async.js非常著名的节点,可能是由于统一的错误处理协议节点。
在前端,没有统一的会议开始,所以async.js可能需要使用现有的库封装。
事实上,Async.js增加了一层包装,回调函数的几种常见的使用模式。例如,我们需要三个前、后相关的异步操作,包括使用纯的回调函数:
AsyncOpA(A,B,(呃,结果)= > {
如果(错误){
HandleErrorA(ERR);
}
AsyncOpB(C,结果,(呃,结果)= > {
如果(错误){
HandleErrorB(ERR);
}
AsyncOpB(D,结果,(呃,结果)= > {
如果(错误){
HandlerErrorC(ERR);
}
finalop(结果);
});
});
});
如果我们使用异步库来做:
Async.waterfall({
= {(CB)
AsyncOpA(A,B,(呃,结果)= > {
CB(错误,C,结果);
});
},
(C,lastresult,CB)= > {
AsyncOpB(C,lastresult,(呃,结果)= > {
CB(错误,D,结果);
})
},
(d,lastresult,CB)= > {
AsyncOpC(D,lastresult,(呃,结果)= > {
CB(错误,结果);
});
}
},(呃,最终结果)= > {
如果(错误){
HandlerError(ERR);
}
finalop(最终结果);
});
如您所见,回调函数从原来的横向开发转换为垂直开发,并将错误均匀地传递给最终处理函数。
其原理是将函数中的后一个函数封装为前一个函数的最后一个参数CB,并请求:
各职能部门应执行其CB参数;对CB的第一个参数是用来传递错误。我们可以自己写一个async.waterfall实施:
让async = { {
瀑布:(方法,finalcb = _emptyfunction = > {)
如果(!_isarray(方法)){
返回finalcb(新的错误(第一论点瀑布必须是一个数组));
}
如果(!方法(长度){
返回finalcb();
}
函数包装(n){
如果(n =方法长度){
返回finalcb;
}
返回函数(错误),…args){
如果(错误){
返回finalcb(ERR);
}
方法{ }(…一、包装(n + 1));
}
}
包装(0)(假);
}
};
async.js还具有多种过程控制方法等系列 / /平行而共同实现异步协作。
异步js的问题:
它仍然不能摆脱外部的回调函数,只是将它从水平更改为垂直,或者需要程序员熟练的异步回调样式。
仍然没有使用上层catch捕获和抛出错误处理,这取决于一个约定,即回调函数的第一个参数用于传递错误。
2、承诺计划
ES6的承诺是承诺 / +。使用异步过程控制的承诺,还有应注意的几个问题,
为了实现前面提到的函数,我们需要先包装异步函数,这样它就可以返回到一个承诺中:
功能topromisestyle(FN){
收益= > {(参数…)
返回新的承诺((解析,拒绝)= {
FN(args,…(错误,结果)= {
如果(错误)拒绝(错误);
解决(结果);
})
});
};
}
此函数可以将符合以下规则的异步函数转换为返回到希望的函数:
回调函数的第一个参数用于传递错误,第二个参数用于传递正常结果,然后操作即可完成:
让{ OPA,OPB、OPC } = { asyncopa,asyncopb,asyncopc }。地图((FN)= topromisestyle(FN));
零帕(A,B)
然后((=){
返回OPB(C,RES);
})
然后((=){
返回OPC(d,RES);
})
然后((=){
返回finalop(RES);
})
catch((错误)= {
HandleError(ERR);
});
通过承诺,原始异步回调函数样式更像是同步编程风格。我们只需要把结果和当时的方法联系起来,回报也就有了相应的意义。
在每一个随后的onfullfilled功能(和onrejected),还将为下一onfullfilled函数参数的一个很好的价值(和onrejected)然后。
因此,可以使用返回和尝试捕获/抛出,但catch是一种方法的形式,或者不那么令人满意。
3、发电机方案
发电机由6可以理解为将控制传递到其他代码在运行的能力,和返回功能,将继续在需要的时候。在有限范围的功能可以通过使用发生器的实现。
将生成器与承诺相结合,异步代码可以进一步转换为同步样式:
功能* getResult(){
设a,b,c,d;
{试
RES =产量(A,B)OPA;
RES =产量OPB(C,RES);
收率OPC(d);
返回水库;
} catch(错误){
返回handleError(ERR);
}
}
但是,我们还需要一个能自动运行生成器的函数:
功能菌种(日内瓦,…args){
返回新的承诺((解析,拒绝)= {
让创=日内瓦(…args);
函数下一个(FN){
{试
设r=FN();
如果(r.done){
解决(r.value);
}
promise.resolve(r.value)
然后((v)= { {
(下一步)= {
返回gen.next(V);
});
}
(下一步)= {
返回gen.throw(ERR);
})
});
} catch(错误){
拒绝(错误);
}
}
(下一步)= {
返回gen.next(不确定);
});
});
}
使用此函数调用生成器:
产卵(getResult)
然后((=){
finalop(RES);
})
catch((错误)= {
HandleFinalOpError(ERR);
});
可以看出,尝试捕获和返回实际上返回了具有原始功能的代码,并且代码形式没有异步样式的踪迹。
类似的功能与共/ task.js和其他库的实现。
4、异步/等待ES7
ES7将介绍异步功能等关键词,利用这个功能,我们可以轻松的写同步代码,
同时,仍然可以使用原始的异步I/O机制。
异步函数,我们可以写上这样的代码:
异步函数getResult(){
设a,b,c,d;
{试
分辨率=(a,b)等待OPA;
RES =等待OPB(C,RES);
等待OPC(d);
返回水库;
} catch(错误){
返回handleError(ERR);
}
}
GetResult();
它与生成器承诺方案看起来不太一样,但是密钥被更改了。
事实上,异步函数发生器的方案的正式认可,这是作为一个语言内置函数。
异步函数的缺点:
等待仅能在异步功能的使用,所以一旦你写了几个异步功能或使用异步函数库,你可能需要更多的异步函数。
异步功能,这是目前在提案阶段,还没有通过任何浏览器或节点的支持。js / io.js.babel转码也需要开放的实验方案,并对非发电机的浏览器,我们需要引入一个厚的蓄热运行。要使前端生产环境得以应用还需要时间。
以上是本文的全部内容,希望能对大家有所帮助。
声明:本文内容用于数码产品信息整理与选购参考,具体价格、库存、售后政策以官方渠道和电商页面实时信息为准。