Цикл событий: знаете ли вы порядок, в котором они печатаются?

внешний интерфейс опрос

предисловие

существует«Точки знаний о браузере (13) Различное время выполнения обратного вызова: макрозадачи и микрозадачи»В конце этой статьи есть несколько печатных вопросов для интервью. Я подумал, что это было недостаточно весело, поэтому я нашел несколько соответствующих вопросов для интервью и запустил их на разных версиях Node.js. Надеюсь, вам это тоже понравится.

Давайте сначала рассмотрим процесс цикла событий браузера:

  • выполнить первымСинхронизированный код в текущем стеке вызовов(макрозадача);
  • После того, как стек вызовов опустеет, проверьте, есть ли асинхронные задачи (микрозадачи), которые нужно выполнить;
  • если такЗавершить выполнение текущего асинхронного кода(все микрозадачи в очереди микрозадач в текущей макрозадаче),
  • а потомВзять следующую задачу макроса из очереди сообщенийВыполнить (сохранить стек вызовов), а затем запустить новый раунд Event Lopp.

Затем разница в цикле событий между разными версиями Node.js:

  • еслиУзел 10 и ранее: в очереди макрозадач есть несколько макрозадач, и микрозадачи в микроочереди не будут выполняться до тех пор, пока не будут выполнены все макрозадачи в очереди макрозадач.
  • еслиУзел 11 и выше: после выполнения задачи макроса в соответствующей очереди макросов на этапе (setTimeout,setIntervalа такжеsetImmediateОдин из трех, исключая ввод-вывод), немедленно выполняет очередь микрозадач, выполняет все микрозадачи в микроочереди, а затем возвращается к предыдущей очереди макросов для выполнения следующей макрозадачи. ЭтоСовместимо с работой на стороне браузера.

Для этой зависимости я намеревался использоватьnvm(nvm install 10.13.0)установлены10.13.0Версия Node.js

image.png

Конкурсанты в основном представители макро задачsetTimeoutи микрозадачиPromiseи выскочкаasync/awiat.

базовая версия

Первая версия для одной задачи:

setTimeout:

console.log('script start');
setTimeout(() =>  {
  console.log('setTimeout1')
}, 100)
setTimeout(() =>  {
  console.log('setTimeout2')
}, 50)
console.log('script end');

в хроме естьProcessDelayTaskфункция, функция рассчитает задачи со сроком на основе времени запуска и времени задержки, а затем последовательно выполнит эти задачи со сроком. Порядок выполнения следующий:

image.png

Promise:

console.log('script start');
new Promise((resolve) =>  {
  console.log('promise1');
  resolve();
  console.log('promise1 end');
}).then(() => {
  console.log('promise2');
})
console.log('script end');

существуетPromiseвнутриСинхронное выполнение, поэтому будет следующий порядок печати:

image.png

async/awiat:

async function async1() {
  console.log('async1 start')
  await async2();
  console.log('async1 end')
}
async function async2() {
  console.log('async2')
}
console.log('script start')
async1();
console.log('script end')

Порядок выполнения следующий, здесь следует отметить, чтоasync2Функции также выполняются синхронно.

image.png

Вышеупомянутая базовая версия — это разница между синхронной и асинхронной, потому что это одна задача и нет других сложных сценариев, производительность в Node.js такая же, как и в браузерах.

Комбинированный вариант один (setTimeoutа такжеPromise)

console.log('script start');
setTimeout(() =>  {
  console.log('setimeout');
}, 0)
new Promise((resolve) =>  {
  console.log('promise1');
  resolve();
  console.log('promise1 end');
}).then(() => {
  console.log('promise2');
})
console.log('script end');

Порядок выполнения в Chrome:

image.png

Эта версия представляет собой просто задачу макросаsetTimeoutи микрозадачиPromiseКомбинация , поскольку это также макрозадача и микрозадача, она ведет себя одинаково в разных версиях Node.js, как и в браузере.

Комбинированная версия два (setTimeoutа такжеPromise,async/await)

console.log('script start')
async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2')
}
async1()
new Promise(resolve => {
  console.log('promise1 start')
  resolve()
  console.log('promise1 end')
}).then(() => {
  console.log('promise2')
})
console.log('script end')

Порядок выполнения в Chrome:

image.png

в Node.js10.13.0Порядок выполнения в:

image.png

в Node.js12.18.3Порядок выполнения в:

image.png

Не удивляйтесь! Не удивительно! Chrome (версия 91) и Node.js12.18.3Поведение версии соответствует Node.js.10.13.0Есть некоторые отличия в версиях, в основномpromise2а такжеasync1 endПорядок печати другой, то естьasync/awaitjs обрабатывается по-разному в разных версиях Node.js (после 70 и до Chrome 70 также по-разному).

Чтобы понять разницу, перейдите кpromise, async, await, execution orderпроверить это.

Смешанная версия первая (setTimeoutа такжеPromise)

console.log('script start')
setTimeout(() => {
  console.log('setTimeout1')
  Promise.resolve().then(() => {
    console.log('promise1')
  })
}, 0)
setTimeout(() => {
  console.log('setTimeout2')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('script end')

Порядок выполнения в Chrome:

image.png

в Node.js10.13.0Порядок выполнения в:

image.png

в Node.js12.18.3Порядок выполнения в:

image.png

Эта смешанная версия в основном рассматривает разницу между циклом событий до и после Node.js версии 10.

Смешанная версия два (setTimeoutа такжеPromise)

setTimeoutа такжеPromise:

console.log('script start')
Promise.resolve().then(() => {
  console.log('promise1')
  setTimeout(() => {
    console.log('setTimeout1')
  }, 0)
})
setTimeout(() => {
  console.log('setTimeout2')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('script end')

Порядок выполнения в Chrome:

image.png

в Node.js10.13.0Порядок выполнения в:

image.png

в Node.js12.18.3Порядок выполнения в:

image.png

Эта версия выполняет синхронный код (script end), на этот раз первыйPromise(promise1) в очереди микрозадач второйsetTimeoutОн добавлен в хвост очереди сообщений (delay queue), на этот раз для выполнения микрозадач, то есть печатиpromise1, то поставить первыйsetTimeoutДобавить в хвост очереди сообщений (очередь задержки), чтобы оно было напечатано первымsetTimeout2,promise2распечатать позжеsetTimeout1.

Смешанная версия три (Promise)

Комбинация макрозадач и микрозадач почти одинакова, давайте взглянем на сочетание микрозадач!

async function async1() {
  console.log('async1 start');
  Promise.resolve(async2()).then(() => {
    console.log('async1 end');
  })
}
async function async2() {
  console.log('async2');
  Promise.resolve(async3()).then(() => {
    console.log('async2 end');
  })
}
async function async3() {
  console.log('async3');
  Promise.resolve().then(() => {
    console.log('async3 end');
  })
}
console.log('script start');
async1();
new Promise((resolve) =>  {
  console.log('promise1');
  resolve();
}).then(() => {
  console.log('promise2');
});
console.log('script end');

Порядок выполнения в Chrome:

image.png

в Node.js10.13.0Порядок выполнения в:

image.png

Это также легко понять: в соответствии с порядком входа и выхода из стека вызовов код синхронизации выполняется первым.async3только тогда, когдаasync3 endПрисоединяйтесь к очереди микрозадач, затемasync3()функция выскакивает из стека, возвращается вasync2,Пучокasync2 endПрисоединяйтесь к очереди микрозадач, и то же самое верно для следующих, так что есть этот порядок печати.

Смешанная версия четыре (Promiseа такжеasync/await)

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2');
  await async3()
  console.log('async2 end')
}
async function async3() {
  await console.log('async3');
  console.log('async3 end')
}
console.log('script start');
async1();
new Promise((resolve) => {
  console.log('promise1');
  resolve();
}).then(() => {
  console.log('promise2');
});
console.log('script end');

Порядок выполнения в Chrome:

image.png

в Node.js10.13.0Порядок выполнения в:

image.png

Отличие четвертой версии от третьей состоит в том, чтоasyncвнутри функцииPromiseзаменяетсяawait, а в версии триasyncНе работаетawait, вы можете поставитьfunctionпереднийasyncНе обманывайтесь, если знак будет удален.

Синхронный код выполняется дляscript endЯ думал, что это было хорошо раньше.asyncСуществуютawait, поэтому код можно понимать как следующую форму:

Promise.resolve().then(() => {
  console.log('async3 end');
  Promise.resolve().then(() => {
    console.log('async2 end');
    Promise.resolve().then(() => {
      console.log('async1 end');
    })
  })
})
Promise.resolve().then(() => {
  console.log('promise2');
})

Смешанная версия пять (Promiseа такжеasync/await)

function async1() {
  console.log('async1 start');
  Promise.resolve(async2()).then(() => {
    console.log('async1 end');
  })
}
function async2() {
  console.log('async2');
  Promise.resolve(async3()).then(() => {
    console.log('async2 end');
  })
}
async function async3() {
  await console.log('async3');
  console.log('async3 end');
}
console.log('script start');
async1();
new Promise(function (resolve) {
  console.log('promise1');
  resolve();
}).then(function () {
  console.log('promise2');
});
console.log('script end');

Порядок выполнения в Chrome:

image.png

в Node.js10.13.0Порядок выполнения в:

image.png

Через тест предыдущей версии 4 вам здесь не должно быть сложно, есть толькоasync3Eстьawait, выполнять доscript endТак же, как и раньше, вот и мыasync3 endКод позади может быть преобразован в следующую форму мышления:

Promise.resolve().then(() => {
  console.log('async3 end');
  Promise.resolve().then(() => {
    console.log('async2 end');
  })
})
Promise.resolve().then(() => {
  console.log('async1 end');
})
Promise.resolve().then(() => {
  console.log('promise2');
})

Суммировать

На бумаге в конце концов я чувствую себя мелким, и я абсолютно точно знаю, что это дело должно быть сделано.

Связанные статьи о циклах событий и макрозадачах, написанных ранее:

«Очки знаний о браузере (12) Механизм цикла событий (цикл событий)»

«Точки знаний о браузере (13) Различное время выполнения обратного вызова: макрозадачи и микрозадачи»

«Обещание асинхронного программирования: от использования до рукописной реализации (4200 слов)»

«Идеальное решение для асинхронного программирования async/await: писать асинхронный код синхронным способом»