Недавно я смотрел цикл событий js. Цикл событий является ядром работы js. js является однопоточным. Асинхронные события js зависят от механизма цикла событий. Я нашел некоторую информацию в Интернете и нашел эту статью в облаке Tencent.цикл событий jsНаписание очень подробное.Нижеследующее основано на этой статье, плюс некоторые мои собственные выводы.
цикл событий
Во-первых, давайте объясним, что такое цикл событий: Насколько нам известно, js браузера однопоточный, то есть одновременно выполняется не более одного сегмента кода, но браузер хорошо справляется с асинхронными запросами, так почему?
Относительно исполняемого потока:
- Основной поток: то есть поток, выполняемый движком js. Существует только один поток. Отрисовка страницы и обработка функций выполняются в этом основном потоке.
- Рабочий поток: также известный как закулисный поток, этот поток может существовать в браузере или движке js отдельно от основного потока и обрабатывать асинхронные события, такие как чтение файлов и сетевые запросы.
Давайте посмотрим на картинку (эта картинка взята с http://www.zcfy.cc/article/node-js-at-scale-understanding-the-node-js-event-loop-risingstack-1652.html)
Из приведенного выше рисунка видно, что основной поток js имеет стек выполнения, и весь код js будет выполняться в стеке выполнения. Посмотрим на стек выполнения в браузере
Если во время выполнения кода встречается какой-то асинхронный код (такой как setTimeout, ajax, promise.then и пользовательские клики и т. д.), то браузер поместит эти коды в другой поток (здесь мы называем его фоновым потоком) Для выполнения он выполняется нижним слоем браузера на переднем конце и выполняется libuv на конце узла.Выполнение этого потока не блокирует выполнение основного потока, и основной поток продолжает выполнять оставшийся код в стеке.
Когда код в фоновом потоке выполняется (например, время setTimeout истекло и на запрос ajax получен ответ), поток поместит свою функцию обратного вызова в очередь задач (также известную как очередь событий, очередь сообщений) и подождите. реализуйте. Когда основной поток выполнит весь код в стеке, он проверит, есть ли задача для выполнения в очереди задач, и если есть задача для выполнения, поместит задачу в стек выполнения для выполнения . Если текущая очередь задач пуста, она будет продолжать ждать появления задачи в цикле. Следовательно, это называется циклом событий.
очередь задач
Ну вот и вопрос. Если в очереди задач много задач, какую задачу следует выполнить первой? На самом деле (как показано на рисунке выше) в js есть две очереди задач, одна называется Macrotask Queue (Очередь задач) big task, а другая называется Microtask Queue small task
Общие задачи Макротаска:
- setTimeout
- setInterval
- setImmediate
- I/O
- Взаимодействие с пользователем, рендеринг пользовательского интерфейса
Общие задачи Микрозадачи:
- Обещание (выделение)
- process.nextTick(nodejs)
- Object.observe (устарело)
Итак, в чем конкретно разница между ними? Или, если есть две задачи одновременно, какую выбрать?
На самом деле поток выполнения цикла событий выглядит следующим образом:
- Проверить, пуста ли очередь макрозадач, если нет, перейти к следующему шагу, если она пуста, перейти к 3
- Берем задачу в голове очереди (в очереди с наибольшим временем) из очереди макрозадач и выполняем ее в стеке выполнения (только одну), а после выполнения переходим к следующему шагу
- Проверить, пуста ли очередь микрозадач, если нет, перейти к следующему шагу, в противном случае перейти к 1 (запустить новый цикл обработки событий)
- Берем задачу во главе очереди (в очереди с наибольшим временем) из очереди микрозадач и выполняем ее в очереди событий, после выполнения переходим к 3 Среди них вновь добавленная задача микрозадачи в процессе выполнения кода будет выполняться в текущем цикле событий, а вновь добавленная задача макрозадачи может быть выполнена только после следующего цикла событий.
Короче говоря, цикл событий выполняет только задачу в начале очереди макрозадач и немедленно выполняет все задачи в очереди микрозадач после завершения выполнения.
Давайте сначала посмотрим на кусок кода
console.log(1)
setTimeout(function() {
//settimeout1
console.log(2)
}, 0);
const intervalId = setInterval(function() {
//setinterval1
console.log(3)
}, 0)
setTimeout(function() {
//settimeout2
console.log(10)
new Promise(function(resolve) {
//promise1
console.log(11)
resolve()
})
.then(function() {
console.log(12)
})
.then(function() {
console.log(13)
clearInterval(intervalId)
})
}, 0);
//promise2
Promise.resolve()
.then(function() {
console.log(7)
})
.then(function() {
console.log(8)
})
console.log(9)
Как вы думаете, каким должен быть результат? Результаты, которые я вывожу в среде узла и консоли Chrome, следующие:
1
9
7
8
2
3
10
11
12
13
В приведенном выше примере
- Первый цикл событий:
- console.log(1) выполняется, вывод 1
- settimeout1 выполняется, присоединяется к очереди макрозадач
- setinterval1 выполняется, присоединяется к очереди макрозадач
- settimeout2 выполняется, присоединяется к очереди макрозадач
- promise2 выполняется, и две его функции then добавляются в очередь микрозадач
- console.log(9) выполняется, вывод 9
- Согласно определению событийного цикла, следующей будет выполняться только что добавленная задача микрозадачи, в соответствии с порядком постановки в очередь будут выполняться console.log(7) и console.log(8), а также 7 и 8. быть выходным. Очередь микрозадач пуста, вернитесь к первому шагу и войдите в следующий цикл событий.В это время очередь макрозадач: settimeout1,setinterval1,settimeout2
- Второй цикл событий:
Берем задачу во главе очереди (settimeout1) из очереди макрозадач и выполняем ее, выход 2 Очередь микрозадач пуста, вернитесь к первому шагу и войдите в следующий цикл событий.В это время очередь макрозадач: setinterval1,settimeout2
- Третий цикл событий:
Возьмите задачу (setinterval1) в начале очереди из очереди макрозадач и выполните ее, выведите 3, а затем добавьте вновь сгенерированный setinterval1 в очередь макрозадач. Очередь микрозадач пуста, вернитесь к первому шагу и войдите в следующий цикл событий.На данный момент очередь макрозадач: settimeout2,setinterval1
- Четвертый цикл событий:
Возьмите задачу во главе очереди (settimeout2) из очереди макрозадач и выполните ее, выведите 10 и выполните функцию в новом промисе (функция в новом промисе — это синхронная операция, а не асинхронная операция), выведите 11, и поместите его два A, затем функция будет добавлена в очередь микрозадач Из очереди микрозадач возьмите задачу во главе очереди и выполняйте ее, пока она не станет пустой. Таким образом, две вновь добавленные задачи микрозадач выполняются последовательно, выходные данные 12 и 13, а setinterval1 сбрасывается.
В этот момент и очередь микрозадач, и очередь макрозадач пусты, и браузер всегда будет проверять, пуста ли очередь, ожидая добавления новых задач в очередь. Тут можно подумать, в первом цикле почему макрозадача не выполняется первой? Потому что, согласно процессу, разве мы не должны сначала проверить, пуста ли очередь макрозадач, а затем проверить очередь микрозадач?
причина:因为一开始js主线程中跑的任务就是macrotask任务
Согласно процессу петли события, цикл событий будет выполнять задачу Macrotask, поэтому основной код выполнения кода потоков завершен, он возьмет первую задачу команды, выполненную из очереди Microtask.
Уведомление: Потому что, когда задача микрозадачи реализована, она войдет в следующий цикл событий только тогда, когда очередь микрозадач пуста, поэтому, если она постоянно генерирует новые задачи микрозадачи, это приведет к тому, что основной поток будет выполнять задачу микрозадачи. для выполнения задачи Macrotask, чтобы мы не могли выполнять рендеринг пользовательского интерфейса / операцию ввода-вывода / запрос Ajax, поэтому мы должны избегать этого. В Process.nextTick в NodeJs вы можете установить максимальное количество вызовов, чтобы предотвратить блокировку основных потоков.
Как обрабатывается async/await?
Давайте посмотрим, что выводит этот код в браузере?
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
Этот код болееasync/await
Пока мы понимаем принцип этой асинхронной обработки, мы можем знать порядок их выполнения.
async/await
: Эти два брата на самом деле являются синтаксическим сахаром Promise и Generator, поэтому мы конвертируем их в знакомый Promise
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
// 其实就是
async function async1() {
console.log('async1 start');
Promise.resolve(async2()).then(()=>console.log('async1 end'))
}
Тогда давайте посмотрим общий код после преобразования
async function async1() {
console.log('async1 start');
Promise.resolve(async2()).then(()=>console.log('async1 end'))
}
async function async2() {
console.log('async2');
}
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
Теперь это очень ясно, вывод выглядит следующим образом
/**
* async1 start
* async2
* promise1
* script end
* async1 end
* promise2
* */
проблема с таймером
При этом мы вводим новую проблему, проблему таймеров. Являются ли таймеры реальными и надежными? Например, если я выполняю команду: setTimeout(task, 100), может ли она быть выполнена ровно через 100 миллисекунд? На самом деле, согласно приведенному выше обсуждению, мы можем знать, что это невозможно.
Посмотрим на этот каштан
const s = new Date().getSeconds();
setTimeout(function() {
// 输出 "2",表示回调函数并没有在 500 毫秒之后立即执行
console.log("Ran after " + (new Date().getSeconds() - s) + " seconds");
}, 500);
while(true) {
if(new Date().getSeconds() - s >= 2) {
console.log("Good, looped for 2 seconds");
break;
}
}
Если вы не знаете механизм цикла событий, то примите как данность, что событие в setTimeout будет выполнено через 500 миллисекунд, но на самом деле оно будет выполнено через 2 секунды, причина должна быть известна, основной поток был выполнение задач до тех пор, пока через 2 секунды задача в основном потоке не будет завершена, а затем выполняется задача обратного вызова setTimeout в макрозадаче.
Потому что после выполнения setTimeout(task,100) вы просто гарантируете, что задача войдет в очередь макрозадач через 100 миллисекунд, но это не значит, что она может быть запущена немедленно.Возможно, текущий основной поток выполняет трудоемкую операцию, или это может быть В настоящее время в очереди микрозадач много задач, поэтому использование setTimeout в качестве обратного отсчета не гарантирует точность.
блокирующий или неблокирующий
Касательно вопроса js блокирующий или неблокирующий, думаю можно понять так.Перед этим давайте разберемся в объяснении синхронный,асинхронный,блокирующий или неблокирующий.В интернете видел очень хорошее описание.
Блокировка синхронизации: Сяо Мин смотрел на индикатор выполнения загрузки, и он завершается, когда он достигает 100%.
Синхронный и неблокирующий: после отправки задачи на загрузку Сяомин будет заниматься другими делами.Он будет смотреть на индикатор выполнения каждый раз, когда видит, что задача выполнена на 100%. (опрос)
Асинхронная блокировка: Xiao Ming перешел на программное обеспечение с функцией уведомления о завершении загрузки, и когда загрузка будет завершена, будет слышен звук «динь». Но Сяо Мин все еще ждет звука "динь" (выглядит глупо, не так ли? Глупо)
Асинхронная неблокировка: это все еще программное обеспечение для загрузки, которое будет издавать звук «дзинь».После того, как Сяомин отправит задачу загрузки, он будет делать другие вещи, и он узнает, что она завершена, когда услышит звук «дзинь». (самый остроумный)
Наше объяснение:
- Ядро js по-прежнему заблокировано синхронно, например, посмотрите на этот код
while (true) {
if (new Date().getSeconds() - s >= 2) {
console.log("Good, looped for 2 seconds");
break;
}
}
console.log('end')
console.log('end')
Выполнение должно выполняться после окончания цикла while, если цикл не закончился, то поток блокируется.
- Что касается асинхронных событий js, то из-за механизма цикла событий асинхронные события являются асинхронными и неблокирующими, управляемыми событиями.Приведенные выше каштаны хорошо зарекомендовали себя. Таким образом, nodejs подходит для обработки большого параллелизма, поскольку благодаря циклу событий и механизму очереди задач асинхронные операции обрабатываются рабочим процессом (libuv), а основной поток js может продолжать обрабатывать новые запросы. Недостаток также очевиден, потому что это один поток, поэтому он будет более сложным для вычислений, но эта проблема может быть решена через кластерный режим.
Ссылка на ссылку
Ууху. Сын унаследовал бизнес отца. Цао Цао/статья/кивок…