JavsScript中Promise的错误捕获详解

目录
  • 我们需要在异步任务中准确的进行错误捕获,以便我们可以知道错误出在什么地方
  • 我们再讨论then方法中的第二个参数和Promise.catch方法的区别
  • 题: then方法的连续调用,怎么能够知道是第几个then方法报错了呢。
  • 总结

我们需要在异步任务中准确的进行错误捕获,以便我们可以知道错误出在什么地方

如果对Promise和trycatch不够理解的话,很多时候会出现Promise中的错误无法被捕获的情况,本文来讨论这些情况

try catch

try catch 只能捕获当前上下文中的错误,也就是只能捕获同步任务的情况,如下场景:

try {
    throw "程序执行遇到了一些错误";
} catch(e) {
    console.log(e)
}
// 控制台会输出:程序执行遇到了一些错误

这很好,错误被捕获了,我们可以在程序中进程错误的处理;

但是对于异步的任务,trycatch就显得无能为力,不能正确捕获错误:

try {
    setTimeout(() => {
      throw "程序执行遇到了一些错误"
    })
} catch(e) {
    console.log(e);
}
// 控制台输出:Uncaught 程序执行遇到了一些错误;

又或者这样:

try {
    Promise.reject('程序执行遇到了一些错误');
} catch(e) {
    console.log(e);
}
// 控制台输出:Uncaught (in promise) 程序执行遇到了一些错误

上面的代码都无法正常捕获到错误,因为:trycatch永远捕获的是同步的错误

什么是同步的错误?

当在一个事件循环内,同一个任务队列中出现的错误,对于这个任务所在的上下文而言,就是同步错误。

setTimeoutPromise被称为任务源,来自不同的任务源注册的回调函数会被放入不同的任务队列中。

  • setTimeout中的任务会被放入宏任务
  • Promise中的任务会被放入微任务
    • 拓展:setTimeout是宿主浏览器发起的任务,一般会被放入宏任务
    • 而Promise是由JS引擎发起的任务,会被放入微任务

第一次事件循环中,JS引擎会把整个script代码当成一个宏任务执行,执行完成之后,再检测本次循环中是否存在微任务,存在的话就依次从微任务的任务队列中读取执行完所有的微任务,再读取宏任务的任务队列中的任务执行,再执行所有的微任务,如此循环。

JS的执行顺序就是每次事件循环中的宏任务-微任务的不断切换。

再看setTimeout中抛出的错误,这个错误已经不在trycatch所在的事件循环中了,所以这是一个异步错误,无法被trycatch捕获到。

同理,Promise.reject()此处虽然是同步执行的,但是此处reject的内容却在另一个微任务循环中,对于trycatch来讲也不是同步的,所以这两个错误都无法被捕获。

Promise.reject

要理解Promise.reject首先要了解它的返回值,Promise.reject返回的是一个Promise对象,请注意:是Promise对象

Promise对象在任何时候都是一个合法的对象,它不是错误也不是异常,所以在任何实现,直接对Promise.reject或者一个返回Promise对象的调用直接try catch是没有意义的,一个正常的对象永远不可能触发catch捕获。

假设我们由如下代码:

function getData() {
    Promise.reject('遇到了一些错误');
};
function click() {
    try {
        getData();
    } catch(e) {
        console.log(e);
    }
}
click(); // 我们模拟业务场景中的click事件
// 控制台输出: Uncaught (in promise) 遇到了一些错误

Promise已经通过reject抛出了错误,为什么try catch捕获不到呢?

首先,需要知道,对于一个函数的错误是否可以被捕获到,可以尝试将函数调用的返回值替换到函数调用出,看看是否为一个错误

上面getDate()调用会被替换为undefined

对于一个没有明确return的函数调用,其返回值永远是undefined的,所以代码如下:

function click() {
    try {
        undefined;
    } catch(e) {
        console.log(e);
    }
}

了解js基础的人肯定知道,这不算异常,这个代码会正常执行,不会走到catch中。

可能会有另一种思路,就是将Promise.reject返回出去,那么代码就变成:

function getData() {
  return Promise.reject('遇到了一些错误');
};
function click() {
    try {
        getData();
    } catch(e) {
        console.log(e);
    }
}
click();

Promise.reject返回的是一个Promise对象,它是对象,不是错误。所以在try catch中完成getData()调用后这里会出现一个Promise对象,这个对象是一个再正常不过的对象,不会被catch捕获,所以这个try catch依然是无效的。

于是,又出现一种思路:再调用处使用Promise的catch方法进行捕获,于是代码变成:

function getData() {
  return Promise.reject('遇到了一些错误');
};
function click() {
    try {
        getData().catch(console.log);
    } catch(e) {
        console.log(e);
    }
}
click();

这是可行的,rejext的错误可以被捕获,但这不是try catch的功劳,而是Promise的内部消化,所以这里的try catch依然没有意义。

解决Promise异常捕获

Promise异常是最常见的异步异常,其内部的错误基本都是被包装成了Promise对象后进行传递,所以解决Promise异步捕获整体思路有两个:

  • 使用Promise的catch方法内部消化;
  • 使用async和await将异步错误转同步错误再由try catch捕获

Promise.catch

对于Promise.reject中抛出的错误,或者Promise构造器中抛出的错误,亦或者then中出现的错误,无论是运行时还是通过throw主动抛出的,原则上都可以被catch捕获。

如下:

function getData() {
    Promise.reject('这里发生了错误').catch(console.log);
}
​
function click() {
    getData();
}
​
click();

亦或者在调用处捕获,但这需要被调用的函数能返回Promise对象;

function getData() {
    return Promise.reject('程序发生了一些错误');
}

function click() {
    getData().catch(console.log);
}
click();

上面两个方案都可行,事实上建议在业务逻辑允许的情况下,将Promise都返回出去,以便能向上传递,同时配合**unhandledrejection**进行兜底

async await 异步转同步

使用async和await可以将一个异步函数调用在语义上变成同步执行的效果,这样我们就可以使用try catch去统一处理。

例如:

function getData() {
    return Promise.reject('程序发生错误');
}
async function click() {
    try {
        await getData();
    } catch(e) {
        console.log(e);
    }
}

click();

需要注意的是,如果getData方法没有写return, 那么就无法将Promise对象向上传递,那么调用出的await等到的就是一个展开的undefined, 依旧不能进行错误处理。

注意事项

一个函数如果内部处理了Promise异步对象,那么原则上其处理结果应该也是一个Promise对象,对于需要进行错误捕获的场景,Promise对象应该始终通过return向上传递

兜底方案

一般情况下,同步错误如果没有进行捕获,那么这个错误所在的事件循环将终止,所以在开发阶段没有捕获的错误,使用一种方法进行兜底是很有必要的。

对于同步错误,可以定义window.onerror进行兜底处理,或者使用window.addEventListener('error', errHandler)来定义兜底函数。

对于Promise异常,则可以同步使用window.onunhandledrejection或者window.addEventListener('unhandledrejection', errHandler)来定义兜底函数。

我们再讨论then方法中的第二个参数和Promise.catch方法的区别

Promise中的then的第二个参数和catch有什么区别?

  • reject是用来抛出错误的,属于Promise的方法
  • catch是用来处理异常的,属于Promise实例的方法、
  • 区别

    主要区别就是,如果在then的第一个函数中抛出了异常,后面的catch能捕获到,但是then的第二个参数却捕获不到

    then的第二个参数和catch捕获信息的时候会遵循就近原则,如果是promise内部报错,reject抛出错误后,then的第二个参数和catch方法都存在的情况下,只有then的第二个参数能捕获到,如果then的第二个参数不存在,则catch方法会被捕获到。

题: then方法的连续调用,怎么能够知道是第几个then方法报错了呢。

new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve(1);
    }, 1000)
}).then(res => {
    console.log(res);
    return new Promise((resolve,reject) => {
        reject('第一个then方法报错了');
    })
}).then(res => {
    console.log(res);
    return new Promise((resolve,reject) => {
		reject('第二个then方法报错了');
    })
}).catch(err => {
    console.log(err);
})

总结

到此这篇关于JavsScript中Promise错误捕获的文章就介绍到这了,更多相关JS Promise错误捕获内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript中的Promise使用详解

    许多的语言,为了将异步模式处理得更像平常的顺序,都包含一种有趣的方案库,它们被称之为promises,deferreds,或者futures.JavaScript的promises ,可以促进关注点分离,以代替紧密耦合的接口. 本文讲的是基于Promises/A 标准的JavaScript promises.[http://wiki.commonjs.org/wiki/Promises/A] Promise的用例: 执行规则 多个远程验证 超时处理 远程数据请求 动画 将事件逻辑从应用逻辑中解耦

  • 举例详解JavaScript中Promise的使用

    摘录 – Parse JavaScript SDK现在提供了支持大多数异步方法的兼容jquery的Promises模式,那么这意味着什么呢,读完下文你就了解了. "Promises" 代表着在javascript程序里下一个伟大的范式,但是理解他们为什么如此伟大不是件简单的事.它的核心就是一个promise代表一个任务结果,这个任务有可能完成有可能没完成.Promise模式唯一需要的一个接口是调用then方法,它可以用来注册当promise完成或者失败时调用的回调函数,这在Common

  • JS中Promise函数then的奥秘探究

    Promise概述 Promise对象是CommonJS工作组提出的一种规范,目的是为异步操作提供统一接口. 那么,什么是Promises? 首先,它是一个对象,也就是说与其他JavaScript对象的用法,没有什么两样:其次,它起到代理作用(proxy),充当异步操作与回调函数之间的中介.它使得异步操作具备同步操作的接口,使得程序具备正常的同步运行的流程,回调函数不必再一层层嵌套. 简单说,它的思想是,每一个异步任务立刻返回一个Promise对象,由于是立刻返回,所以可以采用同步操作的流程.这

  • Javascript Promise用法详解

    1.约定 本文的 demo 代码有些是伪代码,不可以直接执行. 没有特殊说明,本文所有 demo 都是基于 ES6 规范. Object.method 代表是静态方法, Object#method 代表的是实例方法.如 Promise#then 代表的是 Promise 的实例方法, Promise.resolve 代表的是 Promise 的静态方法. 2.什么是 Promise? 首先我们来了解 Promise 到底是怎么一回事 Promise 是抽象的异步处理对象,以及对其进行各种操作的组

  • JavaScript Promise 用法

    同步编程通常来说易于调试和维护,然而,异步编程通常能获得更好的性能和更大的灵活性.异步的最大特点是无需等待."Promises"渐渐成为JavaScript里最重要的一部分,大量的新API都开始promise原理实现.下面让我们看一下什么是promise,以及它的API和用法! Promises现状 XMLHttpRequest API是异步的,但它没有使用promise API.但有很多原生的 javascript API 使用了promise: *Battery API *fetc

  • 浅谈js promise看这篇足够了

    一.背景 大家都知道nodejs很快,为什么会这么快呢,原因就是node采用异步回调的方式来处理需要等待的事件,使得代码会继续往下执行不用在某个地方等待着.但是也有一个不好的地方,当我们有很多回调的时候,比如这个回调执行完需要去执行下个回调,然后接着再执行下个回调,这样就会造成层层嵌套,代码不清晰,很容易进入"回调监狱",就容易造成下边的例子: async(1, function(value){ async(value, function(value){ async(value, fu

  • 理解JavaScript中Promise的使用

    Javascript 采用回调函数(callback)来处理异步编程.从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验.于是便有了 CommonJS 的 Promises/A 规范,用于解决回调金字塔问题.本文先介绍 Promises 相关规范,然后再通过解读一个迷你的 Promises 以加深理解. 什么是 Promise 一个 Promise 对象代表一个目前还不可用,但是在未来的

  • JavsScript中Promise的错误捕获详解

    目录 我们需要在异步任务中准确的进行错误捕获,以便我们可以知道错误出在什么地方 我们再讨论then方法中的第二个参数和Promise.catch方法的区别 题: then方法的连续调用,怎么能够知道是第几个then方法报错了呢. 总结 我们需要在异步任务中准确的进行错误捕获,以便我们可以知道错误出在什么地方 如果对Promise和trycatch不够理解的话,很多时候会出现Promise中的错误无法被捕获的情况,本文来讨论这些情况 try catch try catch 只能捕获当前上下文中的错

  • 让微信小程序支持ES6中Promise特性的方法详解

    遇到的问题 微信开发者工具更新版本后, 移除了开发者工具对 ES6 中 Promise 特性原生的支持, 理由是因为实体机器是不支持 Promise 的, 所以我们需要引入第三方的 Promise 库 微信更新日志 解决方案 下载第三方库 在这里我引入的是 Bluebird 库, 可以到Bluebird官网 下载需要的文件,也可以通过本地下载 Bluebrid 提供了两种已经构建好的完整的 Promise 库文件, 未经压缩的 bluebird.js 和已压缩的 bluebird.min.js

  • JavaScript中Promise的执行顺序详解

    目录 前言 代码分析 then 方法何时调用? 总结 前言 最近看到一个 Promise 相关的很有意思的代码: new Promise((resolve) => { console.log(1) resolve() }).then(() => { new Promise((resolve) => { console.log(2) resolve() }).then(() => { console.log(4) }) }).then(() => { console.log(3

  • 基于C++中sprintf的错误总结详解

    sprintf 是个变参函数,使用时经常出问题,而且只要出问题通常就是能导致程序崩溃的内存访问错误.下面对sprintf 常出错误问题进行简单的总结: 1.缓冲区溢出:第一个参数的长度太短了,解决办法:将第一个参数的长度扩大.打印字符串时,尽量使用"%.ns"的形式指定最大字符数char buf[5];sprintf(buf, ":%d", 3246);printf("buf is %s\n", buf); 将buf修改为char buf[6]

  • JavaScript 解决ajax中parsererror错误案例详解

    解决ajax的parsererror错误的终极办法(后台传给前台的数据json问题) 出现这个问题的原因是因为后台传给前台的数据出现了问题,ajax对于json的格式特别的严格 下面是会出现这个问题的ajax请求 $.ajax({ type:'get', url:"{php echo $this->createWebUrl('ajax',array('ac'=>'cunByXiangId'))}", data:{id:id}, dataType:'json',//这个地方是

  • C++ 中消息队列函数实例详解

    C++ 中消息队列函数实例详解 1.消息队列结构体的定义 typedef struct{ uid_t uid; /* owner`s user id */ gid_t gid; /* owner`s group id */ udi_t cuid; /* creator`s user id */ gid_t cgid; /* creator`s group id */ mode_t mode; /* read-write permissions 0400 MSG_R 0200 MSG_W*/ ul

  • Python3网络爬虫中的requests高级用法详解

    本节我们再来了解下 Requests 的一些高级用法,如文件上传,代理设置,Cookies 设置等等. 1. 文件上传 我们知道 Reqeuests 可以模拟提交一些数据,假如有的网站需要我们上传文件,我们同样可以利用它来上传,实现非常简单,实例如下: import requests files = {'file': open('favicon.ico', 'rb')} r = requests.post('http://httpbin.org/post', files=files) print

  • 封装一下vue中的axios示例代码详解

    在vue项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中.他有很多优秀的特性,例如拦截请求和响应.取消请求.转换json.客户端防御cSRF等.所以我们的尤大大也是果断放弃了对其官方库vue-resource的维护,直接推荐我们使用axios库.如果还对axios不了解的,可以移步axios文档. 安装 npm install axios; // 安装axios 好了,下面开始今天的正文. 此次封装用以解决: (

  • Python类中的装饰器在当前类中的声明与调用详解

    我的Python环境:3.7 在Python类里声明一个装饰器,并在这个类里调用这个装饰器. 代码如下: class Test(): xx = False def __init__(self): pass def test(func): def wrapper(self, *args, **kwargs): print(self.xx) return func(self, *args, **kwargs) return wrapper @test def test_a(self,a,b): pr

  • 详解Java中NullPointerException异常的原因详解以及解决方法

    NullPointerException是当您尝试使用指向内存中空位置的引用(null)时发生的异常,就好像它引用了一个对象一样. 当我们声明引用变量(即对象)时,实际上是在创建指向对象的指针.考虑以下代码,您可以在其中声明基本类型的整型变量x: int x; x = 10; 在此示例中,变量x是一个整型变量,Java将为您初始化为0.当您在第二行中将其分配给10时,值10将被写入x指向的内存中. 但是,当您尝试声明引用类型时会发生不同的事情.请使用以下代码: Integer num; num

随机推荐