为什么这里错误不会被抛出?

const promise = new Promise(function (resolve) {
    resolve('ok');
    // setTimeout(function () {
        throw new Error('test');
    // }, 0);
});

promise.then(console.log);

注释去掉之后则可以

阅读 4.3k
4 个回答

终于回到学校了...
题主所说的情况:

const promise = new Promise(function (resolve) {
    resolve('ok');
    // setTimeout(function () {
        throw new Error('test');
    // }, 0);
});

promise.then(console.log);

问题:不能抛出异常.

接下来我们多个情况分析并尝试解释下.

首先给大家抛个源码地址吧
传送门:https://github.com/then/promi...

function Promise(fn) {
  if (typeof this !== 'object') {
    throw new TypeError('Promises must be constructed via new');
  }
  if (typeof fn !== 'function') {
    throw new TypeError('Promise constructor\'s argument is not a function');
  }
  this._deferredState = 0;
  this._state = 0;
  this._value = null;
  this._deferreds = null;
  if (fn === noop) return;
  doResolve(fn, this);
}
//其中doResolve()中对抛出错误进行了处理并调用了相应resolve()/reject()函数来修改自身的状态和值/拒因

注释掉setTimeout的情况下,console.log出"ok"

  1. Promise函数中,doResolve其实与then函数中处理相似,都具有2点的特征,也就是一次性,所以在调用了resolve("ok")后,状态和值已经固定,并传给了then()

  2. then函数的问题,我们看看then函数是怎样的promise2 = promise1.then(onFulfilled, onRejected),传入onFulfilled,onRejected两个函数,返回一个promise对象.具体可以看我博文,现在我把题主传入函数对应的情况拿出来.

  • 如果onFulfilled不是函数且promise1状态为fulfilled,则promise2状态为fulfilled且值与promise1的值相同

  • 如果onRejected不是函数且promise1状态为rejected,那么promise2状态必须为rejected且与promise1的reason拒因相同.

  • onFulfilled和onRejected函数只能在promise状态为fulfilled/rejected时调用一次

我们可以看出promise传递给then,then只接受了onFulfilled函数,所以console中出现了"ok"

注释掉resolve("ok")和setTimeout,console无任何信息

new Promise(fn)中,为了让promise正常生成,在内部进行了错误的捕获,具体函数是这样的

function tryCallTwo(fn, a, b) {
  try {
    fn(a, b);
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}

Promise把传入的fn函数进行了错误的捕获,并且返回IS_ERROR来表示出现了异常,在这个时候异常已经被捕获并处理了,然后根据这个异常我们的promise的状态也由pending->rejected,并且拥有了拒因 异常ex.

这个promise传递给了then函数,然而,我们的then函数中onRejected是undefined的,所以这个then函数直接返回了新的rejected并带有拒因 异常ex 的promise对象(then必须返回一个promise对象.)以其后续处理.

所以console中没有任何信息生成

不注释任何代码,直接执行,console中出现"ok"并接着抛出异常

出现"ok"的原因我们说清楚了,然后抛出异常就是因为setTimeout 0的作用了.
众所周知,JavaScript于浏览器端是单线程执行的,我们的任务是一个队列(event loop),所谓的延时,计时器,触发回调是都是基于计时器的,所谓触发执行其实都是立即排队而已.当setTimeout设置时间为0,也代表的是立即插入队列,不是立即执行,也不保证执行时间,所以这样setTimeout(fn,0)其实表示的不是立即执行,而是你排队吧,类似于将队伍第一个人扔到队尾去了.
所以异常其实是在执行完了promise构建后被抛出的,所以,就出现了console中出现"ok"并抛出异常的现象

其实这里我也不是很清楚,希望有人做个补充

我不知道 promise() 过后 Promise 都干了什么,但是我认为 resolve()reject() 是互斥的,一个运行了另一个就不运行了。

另外,如果在 Promise 回调中抛出异常,是会被封装成 reject() 调用的,需要通过 .catch(callback) 来捕捉。

所以,上面,在注释的情况下,已经 resovle() 了,后面的 throw 被封装成的 reject() 调用就被忽略了,所以没有抛出错误。

问题在于把 throw 放在 setTimeout 里之后,估计 reject() 不能把 setTimeout() 的回调封住,所以错误被直接抛出来了,而且不能被 .catch() 到,因为它已经脱离了 Promise 的控制。

1.首先你要明白一个promise有三种状态,分别是pending, resolved(alias: fulfilled) 和 rejected。 而且一旦状态改变,就不会再变,任何时候都可以得到这个结果。状态改变只有两种可能:从pending变为resolved和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。

2.当你注释掉你的setTimeout之后代码就变成了

const promise = new Promise(function (resolve) {
    resolve('ok');
    // setTimeout(function () {
        throw new Error('test');
    // }, 0);
});

上面的resolve('ok')已经将Promise的状态变成了resolved, 你再throw error是不会被捕获的。

3.为什么加了setTimeout之后就可以了捕获了,因为你加了setTimeout之后, 是指定到下一轮“事件循环”再抛出错误,将冒泡到最外层,成了未捕获的错误。因为此时,Promise已经resolved了,完成了自己的任务,所以这个错误是在Promise函数体外抛出的。 你这个时候抛错误出来会被进程捕获并丢出来的。

不仅是 rejected 或者是 resolved, 就算 Promise 仍处于 pendding 状态,只要是在下一次(next tick)事件轮询中才触发的错误,都不会被 Promise 捕捉到,除非直接使用 reject()

4.解释并改造你的三段代码说明一下

4-1. 下面例子中throw Error会被promise的catch方法捕获!

const promise = new Promise(function (resolve) {
    throw new Error('test');
    resolve('ok');
});

// catch会捕获上面的error
promise.then(console.log).catch(console.error);

4-2. 下面例子中throw Error被忽略!!!!等价于没有抛出错误

const promise = new Promise(function (resolve) {
    resolve('ok');
    throw new Error('test');
});

promise.then(console.log).catch(console.error);

4-1和4-2说明了Promise的状态一旦改变就是永久的改变,一直保持这个状态,之后是无法再改变的。

4-3. 下面例子中throw Error会被process捕获!

const promise = new Promise(function (resolve) {
//    throw new Error('test');
    resolve('ok');
    setTimeout(function () {
        throw new Error('test');
     }, 0);
});


promise.then(console.log).catch(console.error);

// setTimeout里面的error在这里被捕获
process.on('uncaughtException', (err) => {
    console.error('got uncaughtExpection=', err);
})

运行一下上面三段代码,然后你自己稍微再体会一下!!希望对你有帮助。

是不是因为Promise内部的错误不会冒泡的全局,所以需要使用setTimeout方法包装一下?

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题