console.log() 异步还是同步?

132

我目前正在阅读Trevor Burnham所著的Async Javascript。这是一本非常好的书。

他讨论了在Safari和Chrome控制台中,代码片段和console.log都被认为是“异步”的。不幸的是,我无法复制这个结果。以下是代码:

var obj = {}; 
console.log(obj); 
obj.foo = 'bar';
// my outcome: Object{}; 'bar';
// The book outcome: {foo:bar};

如果这是异步的话,我会预期结果是书的结果。console.log()会被放入事件队列,直到所有代码都被执行完毕,然后它才会运行并具有bar属性。

但看起来它正在同步运行。

我是在错误地运行此代码吗?console.log实际上是异步的吗?


@thefourtheye:不,所以我应该只是删除我的评论。 - cookie monster
4
我在Chrome中看到过这种情况。如果你使用console.log()打印一个简单的对象并立即更改对象中的内容,console.log()不总是会显示先前的值。如果你遇到了这种情况,解决方法是将你想要打印的任何内容转换为字符串,因为字符串是不可变的,所以不会受到此问题的影响。基于经验,console.log() 存在一些异步问题,可能与在进程边界之间传递数据有关。这不是预期的行为,但是是console.log()内部工作方式的一些副作用(我个人认为这是一个错误)。 - jfriend00
1
@bergi,我只用了10分钟就找到了这个重复项(尽管我知道确切的名称),可能是因为它已经被关闭了。我们能不能交换一下重复项,这样另一个就成为了重复项...? - Jonas Wilms
1
@JonasWilms 我现在重新开放了这个问题(请参见历史记录)。我认为它们不是彼此的副本,我使用Is Chrome's JavaScript console lazy about evaluating arrays?作为特定涉及数组问题的规范目标。 - Bergi
3个回答

156

console.log没有标准化,因此其行为相当不确定,可以很容易地在开发者工具的版本发布之间进行更改。您的书可能已经过时,我的答案也可能很快就过时了。

对于我们的代码来说,console.log是否异步并没有什么区别,它不提供任何回调或类似机制;您传递的值始终是在调用该函数时引用和计算的。

我们真的不知道会发生什么(好吧,我们可能会知道,因为Firebug、Chrome Devtools和Opera Dragonfly都是开源的)。控制台需要将记录的值存储在某个地方,并将它们显示在屏幕上。渲染肯定会异步发生(通过限制速率来节流更新),对控制台中记录对象的未来交互也会异步发生,比如展开对象属性。

因此,控制台可能会克隆(序列化)您记录的可变对象,或者它将存储对它们的引用。前者在处理深度/大对象时效果不佳。另外,至少在控制台中的初始渲染将可能显示对象的“当前”状态,即记录时的状态-在您的示例中,你看到的是Object {}

然而,当您展开对象以进一步检查其属性时,控制台很可能只存储了对您的对象及其属性的引用,并且现在显示它们将显示它们的当前(已经改变的)状态。如果您点击+,则应该能够看到您的示例中的bar属性。

这是在错误报告中发布的屏幕截图,以解释他们的“修复”:

因此,一些值可能会在记录后的很久才被引用,并且这些值的评估方式是相当“懒惰”的(“需要时”)。最著名的例子是在问题Is Chrome's JavaScript console lazy about evaluating arrays?中处理。
解决方法是始终记录对象的序列化快照,例如通过执行console.log(JSON.stringify(obj))。然而,这仅适用于非循环和相对较小的对象。还可以参考How can I change the default behavior of console.log in Safari?
更好的解决方法是使用断点进行调试,在此处执行完全停止,并且您可以检查每个点的当前值。只使用可序列化和不可变数据进行日志记录。

2
我曾经遇到过console.log不是异步的问题。使用JSON.stringify解决了这个问题。 - russiansummer
@tonix 是的,由于我在答案中所列出的原因,这种行为不太可能改变。它不是 bug,而只是交互式调试器/检查器的工作方式。 - Bergi
4
如果您像这里的评论中提到的一样使用JSON.parse(JSON.stringify(obj)),则会得到一个对象形式的快照,而不是字符串。 - Wilt
1
@Wilt JSON.stringify 不会序列化函数、符号和未定义的值。因此,在调试时可能会丢失关键信息。 - the Hutt
1
我可以确认,截至2022年3月最新版本的Node(v17.6.0),console.log是异步的。我在根脚本和函数内部紧挨着放置了console.logprocess.stdout.write语句。所有的process.std.write都先被打印出来,而所有的console.log都最后被打印出来,没有显示调用时对象的状态。因此,我使用了process.stdout.write(\${JSON.stringify(object, null, 2)}\n`)`。 - Inigo
显示剩余7条评论

3

这并不是对于问题的回答,但对于一些偶然看到此帖并感到有用的人来说可能会很方便,而且评论区无法容纳内容太长:

window.console.logSync = (...args) => {
  try {
    args = args.map((arg) => JSON.parse(JSON.stringify(arg)));
    console.log(...args);
  } catch (error) {
    console.log('Error trying to console.logSync()', ...args);
  }
};

这将创建一个类似于console.log的伪同步函数,但与已接受答案中提到的相同注意事项。

由于目前大多数浏览器的console.log都以某种方式是异步的,因此在某些情况下,您可能希望使用这样的函数。


1
JSON.stringify 不会序列化函数、符号和未定义的值。因此,使用这种方法可能会丢失一些信息。 - the Hutt

1

使用console.log时:

a = {}; a.a=1;console.log(a);a.b=function(){};
// without b
a = {}; a.a=1;a.a1=1;a.a2=1;a.a3=1;a.a4=1;a.a5=1;a.a6=1;a.a7=1;a.a8=1;console.log(a);a.b=function(){};
// with b, maybe
a = {}; a.a=function(){};console.log(a);a.b=function(){};
// with b

在第一种情况下,对象足够简单,因此控制台可以将其“字符串化”并呈现给您;但在其他情况下,由于a太过“复杂”,无法进行“字符串化”,因此控制台将显示内存中的对象,并且是的,当您查看它时,b已经附加到a上。

1
我知道这个问题已经有3年了,但现在我也遇到了同样的问题——序列化对象对我来说太复杂了。我正在捕获一个事件并尝试访问它的数据,但是在代码中它没有数据,但在console.log中它有数据。 - Skeec

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接