【问题标题】:Firefox: Promise.then not called asynchronouslyFirefox:Promise.then 不被异步调用
【发布时间】:2015-09-08 15:34:54
【问题描述】:

我阅读了 Promise/A+ 规范,它在 2.2.4 下说:

在执行上下文堆栈仅包含平台代码之前,不得调用 onFulfilled 或 onRejected

但在 Firefox(我测试了 38.2.1 ESR 和 40.0.3)中,下面的脚本同步执行了 onFulfilled 方法:

var p = Promise.resolve("Second");
p.then(alert);
alert("First");

(这里好像没有使用alerts运行,这里也可以试试:http://jsbin.com/yovemaweye/1/edit?js,output)

它在其他浏览器或使用 ES6Promise-Polyfill 时按预期工作。

我在这里错过了什么吗?我一直认为 then-method 的要点之一是确保异步执行。

编辑:

它在使用 console.log 时有效,请参阅 Benjamin Gruenbaum 的回答:

function output(sMessage) {
  console.log(sMessage);
}

var p = Promise.resolve("Second");
p.then(output);

output("First");

正如他在 cmets 中指出的那样,在使用同步请求时也会发生这种情况,这正是它在您的测试场景中发生的原因。 我创建了一个在我们的测试中发生的事情的最小示例:

function request(bAsync) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.addEventListener("readystatechange", function() {
      if (xhr.readyState === XMLHttpRequest.DONE) {
        resolve(xhr.responseText);
      }
    });
    xhr.open("GET", "https://sapui5.hana.ondemand.com/sdk/resources/sap-ui-core.js", !!bAsync);
    xhr.send();
  });
}

function output(sMessage, bError) {
  var oMessage = document.createElement("div");
  if (bError) {
    oMessage.style.color = "red";
  }
  oMessage.appendChild(document.createTextNode(sMessage));
  document.body.appendChild(oMessage);
}

var sSyncData = null;
var sAsyncData = null;

request(true).then(function(sData) {
  sAsyncData = sData;
  output("Async data received");
});

request(false).then(function(sData) {
  sSyncData = sData;
  output("Sync data received");
});


// Tests
if (sSyncData === null) {
  output("Sync data as expected");
} else {
  output("Unexpected sync data", true);
}
if (sAsyncData === null) {
  output("Async data as expected");
} else {
  output("Unexpected async data", true);
}

在 Firefox 中,这会导致:

【问题讨论】:

  • 返回 "Second" , "First" , "Third" 在火狐?
  • 确切地说,它应该是 Chrome 中的“第一”、“第二”、“第三”——还是我遗漏了什么?
  • 你可以忽略 setTimeout - 我不在乎它是第二个还是第三个(也许我应该把它排除在外) - 我只关心“第一”和“第二”在顺序错误。
  • 请将演示问题的代码粘贴到您的问题中,以便在外部链接早已被更改或失效时,此问题仍然有效。

标签: javascript firefox promise


【解决方案1】:

这是因为您使用的是alert

当您在此处使用 alert 时,它会阻塞并且所有赌注都关闭 - 页面已冻结、执行停止并且处于“平台级别”。

这可能被认为是一个错误,这肯定不是我所期望的 - 但核心是alert 和 JavaScript 任务/微任务语义之间的不兼容。

如果您将该警报更改为 console.log 或附加到 document.innerHTML,您将获得预期的结果。

var alert = function(arg) { // no longer a magical and blocking operation
  document.body.innerHTML += "<br/>" + arg;
}

// this code outputs "First, Second, Third" in all the browsers.

setTimeout(alert.bind(null, "Third"), 0);

var p = Promise.resolve("Second");
p.then(alert);

alert("First");
            
 

据我所知,这实际上是permitted optional behavior:

可选,在等待用户确认消息时暂停。

(强调我的)

基本上,firefox 的作用是这样的:

  • 执行直到遇到第一个alert。
  • 在暂停前运行任何微任务以完成(任务被暂停,而不是微任务)。
  • then 作为微任务运行,因此“Second”进入队列并在alert 之前。
  • Second 警报开始运行。
  • First 警报开始运行。

令人困惑,但据我所知是允许的。

【讨论】:

  • 我不明白 - 你似乎是对的。但是我刚刚创建了带有警报的最小示例,因为我们在根本不使用警报的代码中发现了这种行为......我将尝试创建一个更好的最小示例。如果我没有设法创建一个最小示例或在其他地方找到问题,我会将其标记为正确答案。
  • @sirion 这不是关于alert 特别是,这是关于在暂停事件循环直到任务完成时运行所有微任务以完成。还有其他情况可以触发它(例如同步 ajax)。
  • 同步 ajax 是一个很好的提示——我想我在周围代码的某个地方看到了这一点。
  • 我怀疑是这样介绍的:bugzilla.mozilla.org/show_bug.cgi?id=1094248,这个问题在那里讨论过,读起来很有趣。
  • alert(或其他)同步运行微任务似乎是这里出乎意料的事情。从技术上讲,A+ 禁止处理来自用户代码的队列(alert() 肯定是这样),但只要在.then() 返回后执行回调,我就可以接受任何行为。不过,触发队列应该更加明确:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-23
  • 1970-01-01
  • 1970-01-01
  • 2022-01-17
  • 2017-03-02
相关资源
最近更新 更多