考虑以下 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)
这提出的问题多于答案。
Promise
是 undefined
在没有 promise 的 JS 环境中? callback
在 setTimeout
内? catch
而不是将错误处理程序传递给 then
? setTimeout
什么时候“不能创造 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" .
queueMicroTask
reports any exception将在 callback
期间抛出执行。 Promise 链会“吞下”这个异常(它不会被全局抛出),所以要离开这个 Promise 链,我们必须调用 setTimeout
从.catch()
内部处理程序。 then
的第二个参数处理不会处理回调执行抛出的异常,这正是我们在这里要做的。 Promise
之外的任何其他内容,正如我们在前面的项目符号中所示,它只会在 Promise
的情况下抛出未定义,setTimeout
仅用于将异常抛出 Promise 链。 Promise.resolve()
不会创建 Promise当该功能不是正确的 Promise 实现时。如果是这样的话,它也不可能返回一个可捕获的对象;)但是正如您现在可能已经发现的那样,只有解释文本完全是误导+ing。 现在,关于你的猴子补丁的注释仍然可以改进一点:
catch
+ setTimeout
应该在那里。 queueMicrotask
如果回调不可调用,则应抛出。 .then()
将使用一个参数调用 undefined
, queueMicrotask
不带任何参数地调用它的回调。 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/