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

标签 javascript promise settimeout es6-promise event-loop

考虑以下 polyfill对于 queueMicrotask .

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

MDN 上的描述状态。

It creates a microtask by using a promise that resolves immediately, falling back to using a timeout if the promise can't be created.



queue-microtask库也使用相同的 polyfill。这是它的文档所说的。

  • Optimal performance in all modern environments.
    • Use queueMicrotask in modern environments (optimal)
    • Fallback to Promise.resolve().then(fn) in Node.js 10 and earlier, and old browsers (optimal)
    • Fallback to setTimeout in JS environments without Promise (slow)


这提出的问题多于答案。
  • 不会Promiseundefined在没有 promise 的 JS 环境中?
  • 为什么我们抛出错误而不是调用 callbacksetTimeout 内?
  • 为什么我们使用单独的 catch而不是将错误处理程序传递给 then ?
  • 这个 polyfill 如何回退到使用 setTimeout什么时候“不能创造 promise ”?
  • 什么时候不会创建 promise ?

  • 我本来希望 polyfill 实现如下。

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

    没有实现的原因是什么?

    编辑:我正在浏览 queue-microtask 库的提交历史,发现 this 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))
    

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

    最佳答案

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

    要回答您的问题:

  • Promise将是未定义的,因此 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') );

    但是这个“垫片”显然是 only aimed at "modern engines" .
  • MDN 编辑器 did introduce that exception throwing here这样做是因为规范要求 queueMicroTask reports any exception将在 callback 期间抛出执行。 Promise 链会“吞下”这个异常(它不会被全局抛出),所以要离开这个 Promise 链,我们必须调用 setTimeout.catch() 内部处理程序。
  • then的第二个参数处理不会处理回调执行抛出的异常,这正是我们在这里要做的。
  • 它不会回退到 Promise 之外的任何其他内容,正如我们在前面的项目符号中所示,它只会在 Promise 的情况下抛出未定义,setTimeout仅用于将异常抛出 Promise 链。
  • Promise.resolve() 不会创建 Promise当该功能不是正确的 Promise 实现时。如果是这样的话,它也不可能返回一个可捕获的对象;)但是正如您现在可能已经发现的那样,只有解释文本完全是误导+ing。


  • 现在,关于你的猴子补丁的注释仍然可以改进一点:
  • 这个编辑器实际上是正确的 error should be reported , catch + setTimeout应该在那里。
  • queueMicrotask如果回调不可调用,则应抛出。
  • Nitpick,但回调传递给 .then()将使用一个参数调用 undefined , queueMicrotask不带任何参数地调用它的回调。
  • 再次挑剔,每次检查 Promise 是否可用听起来并不好,要么从一开始就定义了 Promise,要么你将使用你不知道他们如何管理异步性的 polyfill。
  • 更重要的是(?)您可能想要添加对更多环境的支持。


  • queue a microtask在 Promise 进入浏览器之前,算法已经成为 Web 标准的一部分:MutationObserver queues microtasks too ,并且它在 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' ) );


    在 node.js < 0.11 中,process.nextTick()是最接近微任务的,所以你可能也想添加它(它足够短)。
    if( typeof process === "object" && typeof process.nextTick === "function" ) {
      process.nextTick( callback );
    }
    

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

    (function() {
    'use strict';
    
    // lazy get globalThis, there might be better ways
    const globalObj = typeof globalThis === "object" ? globalThis :
      typeof global === "object" ? global :
      typeof window === "object" ? window :
      typeof self === 'object' ? self :
      Function('return this')();
    
    if (typeof queueMicrotask !== "function") {
    
      const checkIsCallable = (callback) => {
        if( typeof callback !== 'function' ) {
          throw new TypeError( "Failed to execute 'queueMicrotask': the callback provided as parameter 1 is not a function" );
        }  
      };
    
      if( typeof Promise === "function" && typeof Promise.resolve === "function" ) {
        globalObj.queueMicrotask = (callback) => {
          checkIsCallable( callback );
          Promise.resolve()
            .then( () => callback() ) // call with no arguments
            // if any error occurs during callback execution,
            // throw it back to globalObj (using setTimeout to get out of Promise chain)
            .catch( (err) => setTimeout( () => { throw err; } ) );
       };
      }
      else if( typeof MutationObserver === 'function' ) {
        globalObj.queueMicrotask = (callback) => {
          checkIsCallable( callback );
          const observer = new MutationObserver( function() {
            callback();
            observer.disconnect();
          } );
          const target = document.createElement( 'div' );
          observer.observe( target, { attributes: true } );
          target.setAttribute( 'data-foo', '');
        };
      }
      else if( typeof process === "object" && typeof process.nextTick === "function" ) {
        globalObj.queueMicrotask = (callback) => {
          checkIsCallable( callback );
          process.nextTick( callback );
        };
      }
      else {
        globalObj.queueMicrotask = (callback) => {
          checkIsCallable( callback );
          setTimeout( callback, 0 );
        }
      }
    }
    })();
    
    queueMicrotask( () => console.log( 'microtask' ) );
    console.log( 'sync' );

    关于javascript - 以下 queueMicrotask polyfill 如何回退到使用 setTimeout?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61569775/

    相关文章:

    javascript - 使用 Express.js 和 axios 进行 HTTP PUT

    javascript - 为什么这个简单的 async - promise - reduce 代码有效?

    c++ - 如何 move 其中包含 Promise 的结构?

    python - 检查服务器是否在线的合理超时是多少?

    javascript - 如何在到达时间设置之前覆盖 setTimeout?

    javascript - 在 javascript 的各种情况下打印 "this"

    javascript - 对象.观察顺序

    javascript - 禁用 JavaScript 时隐藏图像 map 坐标

    javascript - 在 Angular.js 中实现 promise 时如何始终运行一些代码

    javascript - 如何在 javascript 中使用 setTimeout 考虑延迟