TypeError: Illegal invocation in promise caused by console

前言

昨天我在用「微信开发者工具」(使用 nw.js 制作, Chromium v50.0.2661.94)联调代码时遇到了一个错误:Uncaught (in promise) TypeError: Illegal invocation,但点开错误详情后发现,调用栈都在 core-js 中,无法判断错误的来源。

Promise.reject('出错啦')  
  .catch(console.error)

// > Uncaught (in promise) TypeError: Illegal invocation

经过了许久的单步调试还是没有找到出错来源,于是我抱着试试看的态度搜索了一下……
没想到还真有这样出错的:

Illegal invocation - 非法调用

原因

从 SF 上看到,造成错误的原因是:

console 对象的 log/error/info... 这些方法函数,在执行时会检查 this 是否为 console 对象。

.catch(console.error) 中,传入 catch 的参数为函数本身,而 Promise 对象在调用 catch 时,会直接运行 catch 中的函数参数(console.error): Illegal invocation 由上图可以看出,调用 console.error 函数的上下文为 undefined,所以会报 Illegal invocation

Promise.reject('出错啦')  
  .catch(console.error)

// > Uncaught (in promise) TypeError: Illegal invocation

解决办法

办法很简单:

Promise.reject('出错啦')  
  .catch(err => console.error(err))
// 或者
Promise.reject('出错啦')  
  .catch(console.error.bind(console))

前者不涉及到上下文问题,后者用 ES5 的 bind 方法向 console.error 函数绑定 console 对象本身即可。

如果是 ES3 环境(Excuse me?),可以用 polyfill 过的 bind 方法,或者:

window.log = function () {  
  return console.log.apply(console, arguments)
}

真正原因

为什么出现这种报错我半天找不到问题所在呢?
在之前我使用 Promise 的 then/catch 方法都是直接 .catch(console.error),所以抛出 Uncaught (in promise) TypeError 错误并不觉得是这里的问题。

那么就很奇怪了……所以我又在 Chrome 中尝试这段代码:

console.log.bind(this)('foo')  

发现并没有报错: Chrome test

而在「微信 web 开发者工具」(Chromium v50.0.2661.94)中运行,就会直接报错: Chromium v50 test

最后

在分别测试了 Safari, Firefox 还有最新的 Chromium(v57.0.2939.0) 浏览器后,得出了一个结论:

consolelog/info/error 等方法实现,是取决于浏览器 DevTools 的,即不同的 DevTools 向 JS 环境注入的 console 不同。

目前(2016年12月01日),微信 web 开发者工具所使用 nw.js 集成的 Chromium 版本为 v50.0.2661.94,此版本的 console 的方法中不允许调用时上下文不为 console,因此在开发时要注意,避免直接传入 console.log 这样的方法为参数。

最新的 Chromium(v57.0.2939.0) 的 console 实现不会出现上面的问题,可以放心使用。

正在加载 Disqus 评论组件...