关于promise的一个很奇怪的问题
发布于 8 年前 作者 berwin 8680 次浏览 来自 问答

先上代码

var core = new Promise(function (resolve, reject) {
  reject(1);
})

var a = Promise.resolve({
  then: function (resolve, reject) {
    return core.then(resolve, reject).catch(function (err) {
      console.log('1:', err);
    })
  }
}).catch(function (err) {
  console.log('2:', err)
})


a.then(function () {}, function () {
  throw new Error(111)
})

问题: 为什么 throw new Error(111) 抛出的错误无法被console.log('1:', err);这个函数catch而是被外面的 console.log('2:', err) catch??

13 回复

Promise.resolve 和 new Promise不是一个promise,不能混用的

@i5ting 嗯嗯,就是想把thenable转换成promise,就遇到这个问题,,不太理解这个问题是咋回事。。

上面的代码可以换个形式, 如下面

var core = new Promise(function (resolve, reject) {
  reject(1);
})

var a = Promise.resolve({
  then: function (resolve, reject) {
    // 1 这里被执行, 这里的 reject 应该是 下面 `console.log('2:', err)` 那个被函数被包了一层的函数
    return core.then(resolve, reject).catch(function (err) {
	  // 2 由于 core 是 rejected 状态,所以 reject 被执行, 也就是 `console.log('2:', err)`那个函数被包了一层的函数, 同时, 由于这个函数并没有抛出错误, 返回的是 undefined, 所以, ` console.log('1:', err);` 所在的函数不会被执行
	  // 也就是说,上面的 return 返回的是一个 以 undefined 为结果的 promise, 所以下面的 `console.log('3:', val);` 所在函数被执行
      console.log('1:', err);
    })
  }
}).catch(function (err) {
  console.log('2:', err)
}).then(function (val) {
  console.log('3:', val);
}, function () {
  throw new Error(111)
})

原代码中这句有问题: return core.then(resolve, reject).catch(…) core 执行完为 Rejected 状态, 会调用 reject, 但按你的写法 reject 其实是 null, 注意该 then 返回的是其实是不可变值为 undefined 的 Resolve 的 promise, 前面抛错等于是一个未处理的 rejection, 最后在外面被捕获. then() 必须是回调函数格式, 否则均会出现所谓的 promise 穿透的现象. 上面的语句改为: return core.then(function() {}, function(err) {throw err}).catch(function(err) {console.log(‘1:’, err);})就可以捕获为1了.

不对, 刚又看了下, 我理解错了, reject 在这里不是 null, 而是 then: function(resolve, reject) 中传进来的, 对应是外面的 .catch() 中定义的 function(err) {console.log(‘2:’, err)}, 而不是里面的 function(err) {console.log(‘1:’, err)} 跟 promise 穿透没有关系, 就是简单的参数对应的问题

@William17

reject为什么是下面的函数??

function (err) {
  console.log('2:', err)
}

而不是下面这个函数?

function () {
  throw new Error(111)
}

@JackTu

那如果我把.catch()删了变成下面这样

var core = new Promise(function (resolve, reject) {
  reject(1);
})

var a = Promise.resolve({
  then: function (resolve, reject) {
    return core.then(resolve, reject).catch(function (err) {
      console.log('1:', err);
    })
  }
}).then(function () {}, function () {
  throw new Error(111)
})

那我这个111的错误就没有被任何函数捕获,这是为啥呢??

@berwin

// 这部分的 catch 函数先被执行, 所以这个函数被传进去, a.then 是后面的
}).catch(function (err) {
  console.log('2:', err)
})

catch 可以认为是下面

}).then(null, function (err) {
  console.log('2:', err)
})

@berwin 楼上正解 至于你的问题,就是promise编写时的是一个要注意的地方,调用链末尾必须加catch,如下:

var core = new Promise(function (resolve, reject) {
	reject(1);	
})

var a = Promise.resolve({
	then: function (resolve, reject) {
		return core.then(resolve, reject).catch(function (err) {
  		console.log('1:', err);
		})
	}
}).then(function () {}, function () {
	throw new Error(111)
}).catch(err=>console.error(err));

你写的相当于promiseA.then(onResolve, onReject);这样的onReject函数只能捕获前面的promiseA的中的异常,但是无法捕获Promise链中当前一环的onResolve和onReJect方法本身的抛出的错误,那么尾部加一个catch,就可以捕获catch之前所有的未捕获的异常,前提是你的catch函数本身不能抛出异常,所以我们一般习惯链式调用尾部加一个catch,且该catch方法尽量简单~ 最后,Promise.resolve传入的参数为一个thenable对象时,会立即执行这个thenable对象的then方法,所以这里的下一环OnReject方法是上面的而不是下面的

我觉得core这个Promise不在主线的promise链上面,Promise.resolve传入的thenable对象只是触发后续主线promise的桥梁,传入的thenable对象的then函数的参数resolve,reject相当于2个回调,作用就是启动后续的主线的promise,而这2个回调又传入的core这个promise的then,所以后续的主线promise由core触发,由于core是reject状态,所以会触发主线的reject,而这个reject函数一般不会抛出异常也不会返回类似Promise.reject()的对象,所以core后面的catch.(err =>console.log(‘1:’, err))是不可能会触发的

@hyj1991 噢噢噢。。明白了,原来Promise.resolve传入的参数为一个thenable对象时,会立即执行这个thenable对象的then方法。。。。

在 thenable 中跟 promise 构造函数格式类似, 都是 function(resolve, reject), 在这里向后传递不是通过 return, 而是"正确"通过 resolve(value), "错误"通过 reject(err).

我们来看上面 core.then(resolve, reject) 这一句: core 中是 reject(1), 则被 then(resolve, reject) 中的 reject 捕获. 而这个 reject 是 then:function(resolve, reject) 中的 reject! 此时已经把这个错误传到最外层 then() 的第二个回调函数, 即执行 throw new Error(111) 了, 怎么叫没有没有被任何函数捕获呢? 这里之所以会执行抛出 111 错误, 就是因为捕获到了里面的 reject(1) 才执行的.

至于里面的那个 .catch, 因为 core.then(resolve, reject) 执行 reject 返回是 undefined, 实际上并没有任何错误, 所以不会执行 console.log(‘1:’, err) 那里.

里面的 return 其实有点误导人了, 在这里起的作用只是终止作用, 可有可无, 个人猜测楼主是理解成是返回一个 promise 对象给外层了. 记住在构造函数或 thenable 中 function(resolve, reject) 返回结果要么是 resolve, 要么是 reject, 因为这两个语句本身不终止代码执行, 有时候才会使用到 return, 并不是通过 return 来返回一个 promise.

刚开始对回调函数引用的写法可能不习惯, 可以考虑写成标准格式: then: function(resolve, reject) { core.then(function(res) { resolve(res); }, function(err) { reject(err) }); } 估计这样看起来就要明白多了. 以后熟悉了, 再改回去.

回到顶部