Перенесено из сообщества IMWeb, автор: Sun Shiji,Оригинальная ссылка
Эта статья подозревается в том, что она является заголовком, и она содержит много сводной информации о Microtaks. Пожалуйста, используйте ее с осторожностью.
Google Developer Day China 2018 by Jake Archibald
21 сентября 2018 года, хотя я и не участвовал в GDD, мне посчастливилось прочитать статью Baidu @小木小哥Глубокое погружение в цикл событий браузера (GDD@2018), иллюстрации аннотаций яркие, да и код клика в конце текста тоже очень интересный.Всем рекомендую к прочтению. Здесь я беззастенчиво изложу суть статьи и некоторые собственные выводы следующим образом:
асинхронная задача | Функции | общее происхождение |
---|---|---|
Tasks (Macrotasks) | - Когда текущий цикл событий выполняет задачу в очереди - Новая задача, сгенерированная текущим циклом событий, будет добавлена в очередь задач в указанное время для выполнения. |
- setTimeout - setInterval - setImmediate - I/O |
Animation callbacks | - Выполняется до процесса рендеринга (Структура-Макет-Краска) - Текущий цикл событийВыполнить все задачи в очереди - Текущий цикл событийСозданная новая задача будет выполнена в следующем цикле. |
- rAF |
Microtasks | - задачи, которые будут выполняться сразу в конце текущего цикла событий - Текущий цикл событийВыполнить все задачи в очереди - Текущий цикл событийНовые созданные задачи выполняются немедленно |
- Promise - Object.observe - MutationObserver - process.nextTick |
Интуитивно чувствовать макрозадачи и микрозадачи
Прочтите следующее сообщение в статье публичного аккаунта:
Я не согласен с так называемой разницей между mtask и task... Я думаю, что есть только один столбец пары событий, который является задачей.
Особенно для студентов, которые не знакомы с мышлением асинхронного программирования JS, таких как я после перехода с java на javascript два года назад, на самом деле трудно понять эту последовательность асинхронных вызовов.
Но есть один пример, который специально иллюстрирует макрозадачи и микрозадачи:
// 普通的递归, 造成死循环, 页面无响应
function callback() {
console.log('callback');
callback();
}
callback();
Я считаю, что приведенный выше код очень прост для понимания, очень простая рекурсия, потому что цикл событий не освобождается, рендеринг пользовательского интерфейса не может быть выполнен, в результате чего страница не отвечает.
Обычно мы можем использовать setTimeout для преобразования, следующее выполнение мы ставим в асинхронную очередь, оно не будет постоянно занимать вычислительные ресурсы, это то, что мы называем макрозадачами:
// Macrotasks,不会造成死循环
function callback() {
console.log('callback');
setTimeout(callback,0);
}
callback();
Но микрозадачи, сгенерированные обратным вызовом Promise, такие как следующий код, также вызовут бесконечный цикл.
Из вышеизложенного мы также можем знатьКогда новые микрозадачи, сгенерированные циклом событий, будут выполнены немедленно, и текущий цикл обработки событий не завершится, пока не будут выполнены все очереди микрозадач. Поэтому, когда наши микрозадачи генерируют новые задачи, в очереди микрозадач всегда будут задачи, ожидающие выполнения.На этот раз цикл событий никогда не завершится, что приводит к нашему бесконечному циклу.
// Microtasks,同样会造成死循环,页面无响应
function callback() {
console.log('callback');
Promise.resolve().then(callback);
}
callback();
Микрозадачи и обещания A+
Конечно, вышеизложенное развеивает мои опасения по поводу микрозадач (Особенно, когда кто-то придумывает фрагмент кода, который смешивает setTimeout и Promise, чтобы показать вам порядок, в котором выводится код.) в то же время, это также напоминает мне о том, где я, кажется, видел слово о Microtask.
После многих дней поисков я, наконец, написал обобщающую статью о Promise.Правильная поза для открытия обещанийнайти в. В этой статье используется пример, иллюстрирующий, что код нового промиса будет выполнен немедленно и не будет помещен в асинхронную очередь:
var d = new Date();
// 创建一个promise实例,该实例在2秒后进入fulfilled状态
var promise1 = new Promise(function (resolve,reject) {
setTimeout(resolve,2000,'resolve from promise 1');
});
// 创建一个promise实例,该实例在1秒后进入fulfilled状态
var promise2 = new Promise(function (resolve,reject) {
setTimeout(resolve,1000,promise1); // resolve(promise1)
});
promise2.then(
result => console.log('result:',result,new Date() - d),
error => console.log('error:',error)
)
Приведенный выше код выводит
result: resolve from promise 1 2002
Приходим к двум выводам:
- Подтвержденное обещание/A+ в2.3.2 Спецификация
- Код для нового промиса будет выполнен немедленно (время выполнения 2 секунды вместо 3 секунд)
Но в то время я проигнорировал обещание/A+, связанное сСодержание аннотации:
Здесь «код платформы» означает движок, среду и код реализации промиса.На практике это требование гарантирует, что
onFulfilled
andonRejected
выполняться асинхронно, после поворота цикла событий, в которомthen
вызывается и с новым стеком. Это может быть реализовано либо с помощью механизма «макрозадач», такого какsetTimeout
orsetImmediate
, или с механизмом «микрозадач», таким какMutationObserver
orprocess.nextTick
Поскольку реализация промиса считается кодом платформы, она может сама содержать очередь планирования задач или «трамплин», в котором вызываются обработчики.
Да, это мое первое знакомство с MicroTasks, очень жаль, что не влюбился с первого взгляда.
Эта аннотация описывает обещаниеonFulfilled
а такжеonRejected
Выполнение обратного вызова должно только гарантировать, чтоthen
После вызова он может выполняться асинхронно. конкретно реализованныйsetTimeout
аналогичный механизм макрозадач илиprocess.nextTick
Подобные механизмы микрозадач доступны в зависимости от кода платформы.
Зачем нужны микрозадачи
Релевантные статьи, которые может найти поисковая система, в основном указывают на одну«Задачи, микрозадачи, очереди и расписания», Может быть, это место рождения легендарного первородного греха.
Microtasksобычно запланированы для вещей, которые должны происходить сразу после выполняемого в данный момент сценария, например, реакция на пакет действий или выполнение чего-то асинхронного без штрафа за совершенно новую задачу.
Проще говоря, вы хотите отвечать на ряд задач или выполнять асинхронные операции, но не хотите платить за всю асинхронную задачу. В этом случае для планирования этих задач можно использовать микрозадачи.Задачи, которые должны выполняться сразу после завершения текущего исполняемого скрипта.
Очередь микрозадач обрабатывается после обратных вызовов, если в середине выполнения и в конце каждой задачи нет другого JavaScript.Любые дополнительные микрозадачи, поставленные в очередь во время микрозадач, добавляются в конец очереди и также обрабатываются.
Рассматривая макрозадачи и микрозадачи по отдельности, порядок выполнения можно резюмировать следующим образом:
- Вынуть задачу из очереди задач Макротаски и выполнить ее;
- Выньте все задачи из очереди задач «Микрозадачи» и выполняйте их последовательно;
- Этот цикл событий заканчивается, ожидая следующего цикла событий;
С этого аспекта мы также можем понять, почему Promise.then следует реализовывать как микрозадачи.На основе реализации спецификации Promise/A+ (должно быть асинхронное выполнение) обратный вызов также гарантированно будет выполняться быстрее, а не ждать. until Макрозадачи В следующий раз, когда цикл обработки событий может выполняться. Вы можете повторно выполнить приведенный выше пример при сравнении макрозадач и микрозадач, и вы обнаружите, что количество выполнений в единицу времени между ними разное.
Подумайте о некоторых экземплярах Promise, которые объединяют асинхронные и синхронные задачи.Микрозадачи могут гарантировать, что они получат ресурсы для выполнения быстрее, например:
new Promise((resolve) => {
if(/* 检查资源是否需要异步加载 */) {
return asyncAction().then(resolve);
}
// 直接返回加载好的异步资源
return syncResource;
});
Если приведенный выше код предназначен для загрузки удаленных ресурсов, то только в первый раз необходимо выполнить асинхронную загрузку, а все последующие выполнения могут непосредственно считывать содержимое кэша синхронно. Если мы используем микрозадачи, нам не нужно каждый раз ждать еще одного цикла событий, чтобы получить ресурс, новый процесс экземпляра промиса выполняется сразу, и в то же времяonFulfilled
Обратный вызов также завершается в этом цикле событий, что снижает стоимость переключения контекста и повышает производительность.
Однако из приведенной выше ссылки на спецификацию Promise/A+ мы уже знаем, что разные браузеры несовместимы с этой реализацией. Некоторые браузеры (и все меньше и меньше) реализуют функции обратного вызова Promise как макрозадачи, потому что определение Promise исходит из ECMAScript, а не из HTML.
A Job is an abstract operation that initiates an ECMAScript computation when no other ECMAScript computation is currently in progress. A Job abstract operation may be defined to accept an arbitrary set of job parameters.
согласно сСпецификация ECMAScript, нет соответствующего определения микрозадач, есть аналогичноеjobs
Концепция очень похожа на микрозадачи.
Связанные приложения
Vue - src/core/utils/next-tick.jsСуществуют также реализации связанных макрозадач и микрозадач в
let microTimerFunc
let macroTimerFunc
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
} else if (typeof MessageChannel !== 'undefined' && (
isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
} else {
/* istanbul ignore next */
macroTimerFunc = () => {
setTimeout(flushCallbacks,0)
}
}
// Determine microtask defer implementation.
/* istanbul ignore next,$flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
microTimerFunc = () => {
p.then(flushCallbacks)
// in problematic UIWebViews,Promise.then doesn't completely break,but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed,until the browser
// needs to do some other work,e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
} else {
// fallback to macro
microTimerFunc = macroTimerFunc
}