Добавить Автора
Мы знаем, что js выполняется в одном потоке, так как же с этим справляется асинхронный код js? Например, как выводится следующий код:
console.log(1);
setTimeout(function() {
console.log(2);
}, 0);
new Promise(function(resolve) {
console.log(3);
resolve(Date.now());
}).then(function() {
console.log(4);
});
console.log(5);
setTimeout(function() {
new Promise(function(resolve) {
console.log(6);
resolve(Date.now());
}).then(function() {
console.log(7);
});
}, 0);
В случае не запуска можно сначала угадать окончательный вывод, а потом расширять то, что мы должны сказать.
1. Макрозадачи и микрозадачи
Исходя из нашего многолетнего опыта написания ajax: js следует выполнять в порядке операторов, при асинхронности инициируется асинхронный запрос, за которым следует выполнение, а затем выполняется после возврата асинхронного результата. Но как он справляется с этими исполнительными задачами внутренне?
В js задачи делятся на макрозадачи и микрозадачи, Эти две задачи поддерживают очередь соответственно и выполняются с использованием стратегии «первым пришел — первым вышел»! Все задачи, которые выполняются синхронно, выполняются в задаче макроса.
Макрозадачи в основном включают в себя: сценарий (общий код), setTimeout, setInterval, ввод-вывод, события взаимодействия с пользовательским интерфейсом, postMessage, MessageChannel, setImmediate (среда Node.js).
Основные микрозадачи: Promise.then, MutationObserver, process.nextTick (окружение Node.js).
Конкретные этапы операции следующие:
- Взять выполнение задачи из головы задачи макроса;
- Если во время выполнения встретится микрозадача, она будет добавлена в очередь микрозадач;
- После выполнения макрозадачи, есть ли задачи в очереди микрозадач, если есть, выйти и выполнять их одну за другой до завершения выполнения;
- рендеринг графического интерфейса;
- Возвращайтесь к шагу 1, пока задача макроса не будет выполнена;
Эти 4 шага составляют механизм обнаружения циклов событий, который мы называемeventloop
.
Вернемся к коду, который мы сказали выше:
console.log(1);
setTimeout(function() {
console.log(2);
}, 0);
new Promise(function(resolve) {
console.log(3);
resolve(Date.now());
}).then(function() {
console.log(4);
});
console.log(5);
setTimeout(function() {
new Promise(function(resolve) {
console.log(6);
resolve(Date.now());
}).then(function() {
console.log(7);
});
}, 0);
Этапы выполнения следующие:
- Выполнить журнал(1), вывод 1;
- При обнаружении setTimeout добавьте журнал кода обратного вызова (2) в задачу макроса и дождитесь выполнения;
- Выполните console.log(3) и добавьте log(4) в микрозадачу;
- Журнал выполнения (5), вывод 5;
- При обнаружении setTimeout добавьте журнал кода обратного вызова (6, 7) в задачу макроса;
- После выполнения задачи макрозадачи проверьте, есть ли задача в очереди микрозадач, есть ли журнал микрозадач(4) (добавлен на шаге 3) и выполните вывод 4;
- Извлеките выполнение следующего макроса (2), вывод 2;
- После выполнения задачи макрозадачи проверить, есть ли задача в очереди микрозадач, и ее нет;
- Отключить выполнение следующей макрозадачи, выполнить log(6) и затем добавить log(7) в микрозадачу;
- После выполнения макрозадачи создается журнал микрозадач (7) (добавлен на шаге 9) и результат выполнения 7;
Таким образом, окончательный порядок вывода: 1, 3, 5, 4, 2, 6, 7;
Мы реализуем немного трудоемкую операцию в Promise.then, и этот шаг будет выглядеть более очевидным:
console.log(1);
var start = Date.now();
setTimeout(function() {
console.log(2);
}, 0);
setTimeout(function() {
console.log(4, Date.now() - start);
}, 400);
Promise.resolve().then(function() {
var sum = function(a, b) {
return Number(a) + Number(b);
}
var res = [];
for(var i=0; i<5000000; i++) {
var a = Math.floor(Math.random()*100);
var b = Math.floor(Math.random()*200);
res.push(sum(a, b));
}
res = res.sort();
console.log(3);
})
В Promise.then сначала генерируется массив из 5 миллионов случайных чисел, а затем массив сортируется. Запуск этого кода показывает, что: 1 будет выведено немедленно, 3 будет выведено через некоторое время, а затем будет выведено 2. Независимо от того, как долго вы ждете вывода 3, 2 всегда будет выводиться после 3. Это также подтверждает, что третий шаг в операции цикла обработки событий должен дождаться выполнения всех микрозадач перед запуском следующей макрозадачи.
Между тем, вывод этого кода интересен:
setTimeout(function() {
console.log(4, Date.now() - start); // 4, 1380 电脑状态的不同,输出的时间差也不一样
}, 400);
Первоначально он был настроен на вывод через 400 мс, но поскольку предыдущая задача занимала много времени, последующие задачи можно было только отложить и переместить на задний план. Также можно показать, что задержка таких операций, как setTimeout и setInterval, неточна.Эти два метода можно использовать только для задач макроса после 400 мс задачи, но конкретное время выполнения зависит от того, простаивает ли поток.Если в предыдущей задаче есть трудоемкая операция или добавляется бесконечное количество микрозадач, выполнение следующей задачи будет заблокировано..
2. async-await
Из приведенного выше кода также видно, что код в Promise.then принадлежит микросервисам, так как же выполнить код async-await? Например следующий код:
function A() {
return Promise.resolve(Date.now());
}
async function B() {
console.log(Math.random());
let now = await A();
console.log(now);
}
console.log(1);
B();
console.log(2);
По сути, async-await — это просто синтаксический сахар для Promise+generator. Мы перепишем приведенный выше код, чтобы сделать его более понятным:
function B() {
console.log(Math.random());
A().then(function(now) {
console.log(now);
})
}
console.log(1);
B();
console.log(2);
Таким образом, мы можем понять порядок вывода: 1, 0,4793526730678652 (случайное число), 2, 1557830834679 (отметка времени);
3. requestAnimationFrame
requestAnimationFrame также является методом, который выполняется асинхронно, но этот метод не является ни макрозадачей, ни микрозадачей. согласно сMDNОпределение в:
window.requestAnimationFrame()
Сообщите браузеру, что вы хотите выполнить анимацию, и попросите браузер вызвать указанную функцию обратного вызова, чтобы обновить анимацию перед следующей перерисовкой. Этот метод должен передать функцию обратного вызова в качестве параметра, функция обратного вызова будет выполнена до следующей перерисовки браузера.
requestAnimationFrame выполняется до рендеринга GUI, но после микросервиса, но requestAnimationFrame не обязательно должен выполняться в текущем кадре, и браузер решает, какой кадр выполнять в соответствии с текущей стратегией.
4. Резюме
Мы должны помнить два самых важных момента: js — это однопоточный механизм и цикл обработки событий.