以下 queueMicrotask polyfill 如何回退到使用 setTimeout?

Aad*_*hah 3 javascript event-loop settimeout promise es6-promise

考虑以下polyfill for queueMicrotask.

if (typeof window.queueMicrotask !== "function") {
  window.queueMicrotask = function (callback) {
    Promise.resolve()
      .then(callback)
      .catch(e => setTimeout(() => { throw e; }));
  };
}
Run Code Online (Sandbox Code Playgroud)

MDN 上的描述说明。

它通过使用立即解决的承诺来创建微任务,如果无法创建承诺,则回退到使用超时。

队列microtask库也使用相同的填充工具。这是它的文档所说的。

  • 在所有现代环境中的最佳性能。
    • queueMicrotask在现代环境中使用(最佳)
    • 回退到Promise.resolve().then(fn)Node.js 10 及更早版本,以及旧浏览器(最佳)
    • setTimeout在没有 Promise 的 JS 环境中回退(慢)

这提出的问题多于答案。

  • 那岂不Promiseundefined在JS环境,而无需承诺?
  • 为什么我们抛出错误而不是调用callbackinside setTimeout
  • 为什么我们使用单独的catch而不是将错误处理程序传递给then
  • setTimeout当“无法创建承诺”时,这个 polyfill 如何回退到使用?
  • 什么时候不创建承诺?

我本来希望 polyfill 实现如下。

if (typeof window.queueMicrotask !== "function") {
  window.queueMicrotask = callback =>
    typeof Promise === "function" && typeof Promise.resolve === "function"
      ? Promise.resolve().then(callback)
      : setTimeout(callback, 0);
}
Run Code Online (Sandbox Code Playgroud)

没有实施的原因是什么?

编辑:我正在浏览 queue-microtask 库的提交历史记录,我发现了这个 commit

@@ -1,9 +1,8 @@
-let resolvedPromise
+let promise

 module.exports = typeof queueMicrotask === 'function'
   ? queueMicrotask
-  : (typeof Promise === 'function' ? (resolvedPromise = Promise.resolve()) : false)
-    ? cb => resolvedPromise
-      .then(cb)
-      .catch(err => setTimeout(() => { throw err }, 0))
-    : cb => setTimeout(cb, 0)
+  // reuse resolved promise, and allocate it lazily
+  : cb => (promise || (promise = Promise.resolve()))
+    .then(cb)
+    .catch(err => setTimeout(() => { throw err }, 0))
Run Code Online (Sandbox Code Playgroud)

因此,似乎这个库确实回退到使用cb => setTimeout(cb, 0). 不过,这后来被删除了。这可能是一个没有引起注意的错误。至于 MDN 文章,他们可能只是盲目地从这个库中复制了代码片段。

Kai*_*ido 5

你的主要观点完全正确,如果环境中没有 Promise,这个 polyfill 将无法工作,我确实编辑了 MDN 文章,现在称它为“猴子补丁”,因为它就是这样,我删除了对“后备”的引用,因为没有。

回答您的问题:

  • YesPromise将是未定义的,因此 polyfill 只会抛出:

delete window.queueMicrotask;
delete window.Promise;

if (typeof window.queueMicrotask !== "function") {
  window.queueMicrotask = function (callback) {
    Promise.resolve()
      .then(callback)
      .catch(e => setTimeout(() => { throw e; }));
  };
}

queueMicrotask( () => console.log('hello') );
Run Code Online (Sandbox Code Playgroud)
但是这个“垫片”显然只针对“现代发动机”

  • 确实在此处引入异常抛出的 MDN 编辑器之所以这样做,是因为规范要求queueMicroTask报告callback执行期间会抛出的任何异常。Promise 链会“吞下”这个异常(它不会被全局抛出),所以为了摆脱这个 Promise 链,我们必须setTimeout.catch()处理程序内部调用。

  • 从第二个参数的then处理不会处理回调执行抛出的异常,这正是我们在这里想要做的。

  • 除了 ,它不会回退到其他任何东西Promise,正如我们在前面的项目符号中所示,它只会在Promise未定义的情况下抛出,并且setTimeout仅用于将 Exception 抛出 Promise 链。

  • Promise.resolve()当该函数不是正确的 Promise 实现时,将不会创建Promise。如果是这种情况,它也没有机会返回可捕获的对象 ;) 但是正如您现在可能已经发现的那样,只有解释文本完全具有误导性。


现在,关于您的猴子补丁的说明仍然可能会有所改进:

  • 这个编辑器实际上是正确的,应该报告错误catch+setTimeout应该在那里。

  • queueMicrotask如果回调不是Callable ,则应该抛出。

  • 鸡蛋里挑骨头,但传递给回调 .then()将用一个参数调用undefinedqueueMicrotask调用其回调不带任何参数。

  • 再次吹毛求疵,每次检查 Promise 是否可用听起来不太好,要么从一开始就定义了 Promise,要么你将使用一个你不知道他们如何管理异步性的 polyfill。

  • 更重要的是 (?) 您可能希望添加对更多环境的支持。


在 Promises 进入浏览器之前,队列微任务算法已经是 Web 标准的一部分:MutationObserver 队列微任务也是IE11 支持(与 Promises 不同)。

function queueMutationObserverMicrotask( callback ) {
  var observer = new MutationObserver( function() {
    callback();
    observer.disconnect();
  } );
  var target = document.createElement( 'div' );
  observer.observe( target, { attributes: true } );
  target.setAttribute( 'data-foo', '' );
}

Promise.resolve().then( () => console.log( 'Promise 1' ) );
queueMutationObserverMicrotask( () => console.log('from mutation') );
Promise.resolve().then( () => console.log( 'Promise 2' ) );
Run Code Online (Sandbox Code Playgroud)

在 node.js < 0.11 中,process.nextTick()最接近微任务,因此您可能也想添加它(它足够短)。

if( typeof process === "object" && typeof process.nextTick === "function" ) {
  process.nextTick( callback );
}
Run Code Online (Sandbox Code Playgroud)

总而言之,我们改进后的 polyfill 看起来像

if( typeof process === "object" && typeof process.nextTick === "function" ) {
  process.nextTick( callback );
}
Run Code Online (Sandbox Code Playgroud)