同步错误,try catch就可以捕获。
而异步任务的错误,无论是setTimeout 还是promise.then,单独的try catch都无法捕获。
// 并不会被catch所捕获,程序直接报错
function func() {
try {
setTimeout(() => {
throw new Error('opps! error')
}, 1000)
} catch(e) {
console.log(e, 'err')
}
}
func();
因为事件循环的机制,在执行try catch时,遇到setTimeout,视为异步任务并调用WebApis,定时器线程(另外的线程,由宿主环境提供)开始工作,1000毫秒后,将setTimeout的callback加入到任务队列中(注意,是在另外的线程中等待1000毫秒过去然后将setTimeout的callback加入到任务队列中,而不是直接将setTimeout加入到任务队列中)。当前的try catch执行完毕,执行栈退出的时刻,开始在任务队列中读取队列中的callback,同时形成callback相应的执行栈。由于try catch 的执行栈已经退出,catch是无法拿到任务队列中的callback执行栈里抛出的错误的。
同理,promise.then中的错误也是无法try catch的,因为promise.then属于微任务,将在当前执行栈为空后,才去任务队列中查找微任务,微任务形成自己的执行栈,try-catch执行栈早就退出了,所以无法捕获。
一. promise的异常捕获
1.构造函数中
function main1() {
try {
new Promise(() => {
throw new Error('promise1 error')
})
} catch(e) {
console.log(e.message);
}
}
function main2() {
try {
Promise.reject('promise2 error');
} catch(e) {
console.log(e.message);
}
}
// 以上两个方法里的try catch都不能捕获error,因为promise内部的错误不会冒泡出来,直接被promise吞掉了,只能被promise.catch才可以捕获。即使是在catch中再throw error,也可以被后续的catch所捕获。
2.then
中的异常捕获
function main3() {
Promise.resolve(true).then(() => {
try {
throw new Error('then');
} catch(e) {
return e;
}
}).then(e => console.log(e.message));
}
// 可以在回调函数内部进行try-catch,并将error返回,这时的error会传递给下一个then中。
// 或者在then中直接throw error(或者reject),并在catch中捕获
function main4() {
Promise.resolve(true).then(() => {
throw new Error('then');
}).catch(e => console.log(e.message));
}
tip: 当promise的实例resolve后,错误无法被捕获
var promise=new Promise(function(resolve,reject){
resolve('hi');
throw new Error('test');//该错误无法被捕获
})
promise.then(function(e){
console.log('then ---', e)
}).catch(function(e){
console.log('error ---', e)
})
// catch无法捕获,也不会有报错
// 因为promise内部的error都被promise吞掉了,所以无法冒泡到外层。
// 但是promise一旦resolve或者reject了,状态就不会再改变,所以throw error 也无法改变promise已经被resolve (fullfilled)的事实,即不会有效果
// 注意!!!在resolve或者reject后的代码依旧会继续执行的,所以要根据实际情况选择resolve/reject的位置,或者加return
总结:
- promise内部的错误(reject、throw)被promise内部处理,可被catch捕获。
- promise一旦状态完成,不会再改变。resolve后再throw error没有作用。
- resolve/reject后的代码仍然会被执行。
二. async/await 异常捕获
async函数可以保留运行堆栈
const fetchFailure = () => new Promise((resolve, reject) => {
setTimeout(() => {// 模拟请求
if(1) reject('fetch failure...');
})
})
async function main () {
try {
const res = await fetchFailure();
console.log(res, 'res');
} catch(e) {
console.log(e, 'e.message');
}
}
main();
// 当promise中出现异常,会被promise捕获,然后调用generator的throw方法,抛出错误,而async方法可以吧保留运行堆栈,错误被catch捕获。
// async函数可以保留运行堆栈
const a = async () => {
await b();
c();
};
// 上面代码中,b()运行的时候,a()是暂停执行,上下文环境都保存着。一旦b()或c()报错,错误堆栈将包括a()。
async的实现原理
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
所有的async
函数都可以写成上面的第二种形式,其中的spawn
函数就是自动执行器。
下面给出spawn
函数的实现,基本就是前文自动执行器的翻版。
function spawn(genF) {
return new Promise(function(resolve, reject) { // 返回一个promise
const gen = genF(); // 生成器函数,可能调用它的next或者throw方法
function step(nextF) { // 不断next,直到结束
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
// 这里的next.value,即被await的promise请求等异步函数,如果这个promise失败,则进入then的失败回调中,抛错
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); }); // Generator.prototype.throw()把错误抛出来,才能被外层的try-catch捕获到
});
}
step(function() { return gen.next(undefined); });
});
}
总结:
- async返回的是promise,可以被then、catch。async函数本身是个异步的。
- async内部可以使用try-catch捕获异常。
- async内部,遇到await则暂停执行,控制权交给async函数的父级函数,等异步返回后,重新获取控制权,继续执行下方代码。
其他知识点:
宏任务、微任务
在任务队列这里,还有一个小的区分。异步任务分为宏任务(macro task)和微任务(micro task),并且会添加到不同的任务队列中。
tasks
不过查阅 html 规范中,并没有 macro task 的定义(我是看别人文章都这么写),为了严谨性,下面都称为 tasks。 一个事件循环中可能会有一个或多个 tasks,这个是根据 task 源划分的,比如事件(鼠标单击、键盘操作)就是一个 task 源,可能会放到一个任务队列中,XHR 的回调放到一个队列中,但是具体的优先级,这么多 tasks 到底先从哪个取,这个浏览器会根据情况去获取以达到更好的交互体验,先不放到本次研究范围,大概了解 tasks 会按照源去分成多个就好了。 常见的宏任务 tasks 包括:
- XMLHttpRequest 回调
- 事件回调(onClick)
- setTimeout/setInterval
- history.back
宏任务会被加入到下一个事件循环的队列的任务队列中。
micro-task
microtask queue 在每个事件循环中只有一个,跟 tasks 区分,它的本意是尽可能早的执行异步任务。常见的 microtask 包括:
- Promise.then
- MutationObserver
- Object.observe
- process.nextTick(node 中)
Promise.then 在不同的平台实现方式不同,不过大多数都参照 promise/A+ 规范,是当做 microtask 处理的。
microtask 是在一个事件循环结束后(执行栈为空时),立即执行,即 tasks 的一个任务执行后,并且会清空 microtask 队列。另外,如果 microtask 中新添加了 microtask,会放到 queue 末尾一起执行。
当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,执行完毕后,再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
yum install telnet
apk add telnet
apt-get install telnet
各个系统自取安装方式