Как следующая очередь очереди Polymill Microtask может использовать setTimeout? - PullRequest
1 голос
/ 03 мая 2020

Рассмотрим следующее polyfill для queueMicrotask.

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

Описание состояний MDN.

Создает микрозадача с использованием обещания, которое разрешается немедленно, и возвращается к использованию тайм-аута, если обещание не может быть создано.

Библиотека queue-microtask также использует тот же полизаполнение , Вот что говорится в его документации.

  • Оптимальная производительность во всех современных средах.
    • Использование queueMicrotask в современных условиях (оптимально)
    • Откат до Promise.resolve().then(fn) в Node.js 10 и более ранних версиях и старых браузерах (оптимально)
    • Откат до setTimeout в JS средах без Promise (медленно)

Это вызывает больше вопросов, чем ответов.

  • Не будет Promise be undefined в JS средах без обещаний?
  • Почему мы выдаем ошибку вместо вызова callback в setTimeout?
  • Почему мы используем отдельный catch вместо передачи обработчика ошибок в then?
  • Как этот полифилл отступает от использования setTimeout, когда «обещание не может быть создано»?
  • Когда будет обещание не будет создан?

Я бы ожидал, что полифилл будет реализован следующим образом.

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

В чем причина, почему он так не реализован?

Edit: Я просматривал историю коммитов библиотеки 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, возможно, они просто слепо скопировали фрагмент из этой библиотеки.

1 Ответ

0 голосов
/ 05 мая 2020

Вы совершенно правы в своих основных моментах, этот полифилл не будет работать, если в среде нет Promise, и я действительно отредактировал статью MDN, чтобы теперь называть ее «обезьяньим патчем», так как он есть. и я удалил ссылку на «запасной вариант», поскольку нет.

Чтобы ответить на ваши вопросы:

  • Да Promise будет неопределенным, и, таким образом, полифилл просто выдаст:

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') );
Но эта «прокладка», по-видимому, предназначена только для «современных двигателей» .
  • Редактор MDN, который сделал введя здесь исключение, выбрасывающее , сделало это, потому что спецификации спрашивают, что queueMicroTask сообщает о любом исключении , которое будет выброшено во время выполнения callback. Цепочка Promise будет " проглатывать " это исключение (оно не будет выброшено глобально), поэтому чтобы выйти из этой цепочки Promise, мы должны вызвать setTimeout изнутри обработчика .catch().

  • Обработка со вторым параметром then не будет обрабатывать исключения, возникающие при выполнении обратного вызова, и это именно то, что мы хотим сделать здесь.

  • Он не отступает ни к чему другому, кроме Promise, как мы показали в предыдущих пунктах, он просто выбрасывал, если Promise не определен, а setTimeout используется только для выброса исключения из Цепочка обещаний.

  • Обещание не будет создано Promise.resolve(), если эта функция будет чем-то иным, чем правильная реализация Promise. И если это так, то нет никаких шансов, что он вернет также и подхватываемый объект;) Но, как вы уже могли заметить, только текст объяснения был полностью введен в заблуждение.


Теперь, заметка о вашем обезьяньем патче, который все еще может быть немножко улучшен:

  • Этот редактор на самом деле был прав, сообщив об ошибке , catch + setTimeout должен быть там.

  • queueMicrotask должен выдать, если обратный вызов не Вызываемый .

  • Nitpick, но обратный вызов, переданный .then(), будет вызываться с одним аргументом undefined, queueMicrotask вызывает его обратный вызов без каких-либо аргументов.

  • Снова Nitpick, проверяя каждый раз, если Promise доступен, звучит не очень хорошо, либо Promise определяется с самого начала, либо вы будете использовать полифил, который вы не знаете, как они управлял асинхронностью.

  • Что еще более важно (?), вы можете добавить поддержку большего количества сред.


* поставил в очередь микрозадачу * алгоритм 1090 * уже был частью веб-стандартов до того, как Promises попал в браузеры: 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' ) );

В node.js <0,11, <code>process.nextTick() был ближе всего к тому, что представляют собой микрозадачи, так что вы можете захотеть добавить его тоже (оно достаточно короткое).

if( typeof process === "object" && typeof process.nextTick === "function" ) {
  process.nextTick( callback );
}

Таким образом, наш улучшенный полифилл будет выглядеть так:

(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' );
...