Я в ивент-лупе вроде как разбираюсь и с микро и макро тасками тоже давно решил вопрос. Просто интересно было взглянуть на твой ролик. Увидел необычный пример с вызовом тасок по клику, стало интересно взглянуть на остальные ролики и блин мужик, спасибо тебе огромное! Очень интересный контент! Однозначно подписка!=)
Так бывает. Особенно когда и изначально четкого понимания не было, а были догадки навеянные знанием как это устроено в других языках. Более новое видео посмотрели?
Спасибо огромное за видео! Подскажите, пожалуйста, а что будет в случае, если мы напишем setTimeout(() => console.log("Macrotask"), 1000); fetch(someUrl).then(() => console.log("Microtask")); Предположим, что fetch отработает в один момент с setTimeout. Перед выполнением произойдет первая перерисовка интерфейса, следовательно, весь основной код выполнится и добавятся одновременно в Macrotask queue console.log("Macrotask") и в Microtask queue console.log("Microtask"). Неужели в данном случае первым выполнится callback из setTimeout : console.log("Macrotask"), а только после него - console.log("Microtask") из fetch, так как исходя из видео сначала выполняется только одна задача из Macrotask queue (в нашем случае console.log("Macrotask")), а потом все задачи из Microtask queue (console.log("Microtask"))? Заранее благодарю за ответ!
Если «одновременно» (чего а реальной жизни не добиться) то именно так. Сначала вызовется на выполнение код одного макротаска, а потом вызовутся обработчики промисов для всех уже выполненных микротасков. В этом разница. Микротаск выполняется как бы параллельно и когда он уже выполнился происходит вызов обработчика промиса. Именно поэтому результат работы микротасков можно обрабатывать пачками - мы имеем дело с уже выполненной работой, поэтому это легкая задача.
Еще более просто постараюсь написать чтобы избежать путаницы. Порядок такой. Выполняется код одного макротаска, получаем результаты работы всех уже выполненных в фоне микротасков, перерисовываем страницу. Пока выполняется макротаск перейти на следующий шаг нельзя, но код микротасков при этом работает. Если к моменту завершения работы кода макротаска есть уже выполнившиеся микротаски то мы сможем их обработать. Но не раньше завершения макротаска. Микрозадача может выполниться очень быстро, но мы об этом не узнаем до тех пор пока не завершится текущая макро задача.
Мне осталось не понятным - почему обработчик из fetch попадает в макрозадачу, ведь под его капотом используется промис? Почему тогда промис и фетч отрабатывают по разному?
Когда зарезолвиься промис то это будет микротаск. Любой промис, хоть через fetch, хоть через new Promise- это микро задача. Значит выполнится после той макрозадачи во время которой зарезолвилось. Запомнить очень просто. Одна макрозадача, потом все зарезолвившиеся на данный момент микрозадачи, потом ререндер. И т. д. с начала :)
Вы немного не в теме. Nodejs и js в браузере это разное. В браузере нет отдельно работающего независимого «основного» потока. Есть мэйн луп, это да. Берет макротаск из очереди выполняет, берет все ЗАРЕЗОЛВИВШИЕСЯ на этот момент микротаски (не все что есть) и выполняет по очереди, делает ререндер. Если в макротаске создать микротаск, который сразу зарезолвлен, то он выполнится сразу после текущего макротаска, если не зарезолаился еще то выполнится позже ПОСЛЕ какого-то другого макротаска. В js нет многопоточности и многопроцессности, нет прерываний. Все работает в одном потоке последовательно. Если что-то запустили, не важно микро или макро таск, он выполнится строго в свою очередь. Это легко проверить на практике, но кто сейчас проверяет? Проще верить в чудеса.
@@EasyITChannel Не понятно тогда, почему в консоли "promise-4" и "MICRO" идут перед "macro-2"(как и полагается микротаскам), а вот микротаска "promise fetch" идёт после? Ведь "promise fetch" тоже микротаска. Или тут дело в том, что "promise-4" резолвится сразу без ожидания, а "promise fetch" ждёт ответа сервера, и мол, пока сервер отвечает в ивент-луп попадает макротаска "macro-2"?
@@АндрейРосовский Fetch API попадает в очередь макрозадач, а вот его обработка(любая обработка промиса) попадает уже в очередь микрозадач. Стоит запомнить, что Web API - это все макротаски.
Не верно. То что у тайм-аута с нулевой задержкой на самом деле есть реальная задержка в 4мс в данном случае не является причиной. Причина именно такой последовательности срабатывания обработчиков в том, что setTimout создает кроме задержки еще и макротаск. То есть сначала будет задержка на указанное время, а потом функция попадет в очередь макротасков и выполняется по обычным правилам event loop. «Проц» при этом нагружается абсолютно также, просто между отдельными макрозадачами происходит отрисовка интерфейса и пользователю кажется что нет подвисаний как если бы тоже самое делалось в while(true) цикле. Внимательно пересмотрите видео, Вы просто ничего не поняли. За деревьями не увидели леса.
@@EasyITChannel получается что отрисовка каждые 100 по счетчику, а не всё сразу. Это понятно. Но если бы не задержка на сеттаймаут, цикл прошёл бы очень быстро и всё бы зависло. Когда вы стали менять условия в цикле, в конце видео, зависания, доказывают это
Дело не в этом. Чтобы понять в чем причина нужно просто понимать как работает эвент луп (данный ролик как раз об этом). Все довольно просто, не нужно ничего предполагать или придумывать. Например, счетчики. 1. выполняется один макротаск из очереди 2. выполняются обработчики ВСЕХ зарезолвленных микротасков 3. выполняется перерендер UI. 4. переход к пункту 1. Если макротаска нет (представьте, такое бывает), то вызывается обработчики зарезолвленных микротасков (промисов) и т.д. по кругу Сет таймаут с любой задержкой создает макротаск. Функция-обработчик это тело макротаска. Обработчик попадет в очередь после задержки, указанной для сетТаймаут. Пока висит таймаут с этой задачей ничего не происходит. Когда таймаут завершится функция попадет в очередь макротасков как отдельный макротаск. В нашем случае, если задать сетТаймаут 0, то эта функция попадет в очередь сразу и кроме того это гарантированно приведет к тому, что функция сработает ПОСЛЕ как минимум одного ререндера. Но если функция в сетТаймауте будет тяжелая, то на время ее выполнения все зависнет, т.к. это макротаск. Пока он работает к ререндеру JS перейти не может. Именно поэтому и висит, и разгрузка происходит не потому, что у таймаута маленькая задержка (или большая) а потому, что это новый макротаск. Улавливаете? Смысл совсем в другом, не в задержке. Сама по себе задержка ничем не поможет. Если просто завесить макротаск с помощью while(true), то и UI зависнет до его завершения.
@@EasyITChannel теперь понял, спасибо за очень подробное объяснение. Недавно провалил 2 собеседования, одно в вк, другое в уралсиб. Теперь вот изучаю всё, чтобы в следующий чтобы пройти. Про микро и макротаски спрашивали и там и там. В вк еще надо было полифил для Promise.all написать, то есть надо знать всё методы js, уметь написать самому. Ну и всё что на learnjavascript, надо знать, прям хорошо. Не считая regex конечно
Нет, сначала выполнится макрозадача, в которой создали микрозадачи, потом все зарезолвившиеся микрозадачи, потом перерендер, потом следующая макрозадача. Тут нет приоритетов, увы, js однопоточный язык
@@EasyITChannel ну если взять в файлике создать несколько промисов, затем нес колько сеттаймаутов, то сперва отработают промисы, затем сеттаймауты. Что то на подобии: Promise.resolve().then(()=>console.log("in promise1")); setTimeout(()=>{ console.log('in setTimeout1') }, 0); setTimeout(()=>{ console.log('in setTimeout2') }, 0); setTimeout(()=>{ console.log('in setTimeout3') }, 0); setTimeout(()=>{ console.log('in setTimeout4') }, 0); setTimeout(()=>{ console.log('in setTimeout5') }, 0); setTimeout(()=>{ console.log('in setTimeout6') }, 0); Promise.resolve().then(()=>console.log("in promise2")); Promise.resolve().then(()=>console.log("in promise3")); Promise.resolve().then(()=>console.log("in promise4")); Promise.resolve().then(()=>console.log("in promise5")); То вывод будет: in promise1 in promise2 in promise3 in promise4 in promise5 in setTimeout1 in setTimeout2 in setTimeout3 in setTimeout4 in setTimeout5 in setTimeout6
Все верно. Все написанное выполняется в макротаске. В нем Вы создаете уже зарезолвившиеся промисы и другие макротаски. Как только текущий макротаск завершится начнут выполняться все зарезолвившиеся микротаски, потом ререндер, потом следующий (один) макротаск, опять все зарезолвившиеся микротаски, опять ререндер и т. д. Вы (не конкретно Вы лично) всё время забываете о том, что сам выполняемый код это тоже макротаск. Не будет прерывания его работы, нет никаких приоритетов. Поставьте в коде консоль логи после каждого вызова и вы увидите, что код выполнится полностью, то есть завершится текущий макротаск, а уже потом пойдут сообщения из промисов. Почему так, если у микротасков высокий «приоритет»? Ответ прост- нет приоритетов. Программа выполняется последовательно по определенной логике. Код микротаска выполняется асинхронно, но получить его результат можно только последовательно, после таска, во время которого он зарезолвился. Именно поэтому можно обработать пачку микротасков - они уже посчитались, нужно только забрать результат, это быстро.
Дело тут вот в чем. 1. setTimeout, особенно с нулевой задержкой очень часто приводит к лишним вычислениям, т.к. вызов колбеков привязан только ко времени задержки, но мало связан с тем успел браузер реально отрисовать сцену или нет 2. setTimeout вызвается всегда, даже если отрисовывать страницу не нужно (браузер свернут, вкладка не активна) 3. requestAnimationFrame не просто вызывает колбек как можно чаще, но и оптимизирует вызов с точки зрения браузера, что позволяет расходовать ресурсы более рационально. Как результат fps обычно выше, при одинаковых входных условиях. Отрисовка становится более плавной, исключаются ненужные ре-рендеры. 4. requestAnimationFrame не выполняет отрисовок в фоне. Общий вывод из этого такой - если имеем дело с анимацией, то лучше делать выбор в пользу requestAnimationFrame. 60 fps это не мало. Не нужно забывать, что это браузер, а не шутер на нативном движке. Хотя и там 60 fps очень даже не плохой результат.
@@EasyITChannel может я что то не понимаю. В моей голове это так. С requestAnimationFrame Отрисовка. Ожидание . шаг в 100 итераций. Отрисовка. Ожидание. Ещё 100. Отрисовка. С setTimeout. Отрисовка. Столько шагов сколько успеет. Отрисовка. Столько шагов сколько успеет. Получается что черрз timeout можно быстрее массив обойти. Но я так понимаю что если setTimeout запустится и не успеет завершится то отрисовка будет ждать его конца
очень грубо - requestAnimationFrame вызывается когда надо что-то перерисовать и предыдущая отрисовка полностью закончилась, через сетТаймаут создается обычный макротаск, который будет вызван минимум после следующего рендера (это если delay=0) и пока он не завершится отрисовки не будет. В этом случае массив обойти можно, но если это займет много времени, то юзер заметит подвисание интерфейса (потерю отзывчивости). Это раз, а два, если обход массива будет происходить очень быстро, то легко может сложиться ситуация когда макротаск закончился, а предыдущая анимация еще не завершилась (не путать с отрисовкой, ре-рендером) и получается что уже новые макротаск + ре-рендер, а результат вычисления не отличается от предыдущего. Выходит лишняя нагрузка на браузер, лишние перерендеры, когда они и не нужны. Макротаски с работой отрисовки связаны только тем, что выполняются последовательно. Тут есть и обратная сторона медали. Многие думают, что чем выше фпс нем круче. Это не всегда верно. Если это 3Д шутер - обычно да. Но не всегда. Это классическое, увы, заблуждение от непонимания смысла. Если сцена совсем не меняется нет смысла вообще делать ре-нердеры. Они нужны только при каких-то изменениях. Если на экране в центре черный квадрат и он не двигается и юзер ничего не делает, нет смысла этот квадрат перерисовывать с частотой 60фпс. Можно один раз перерисовать и все. Это даже не 1фпс, а 1 фп вечность. :) Вот если он двигается, тогда другое дело. И то зависит от скорости перемещения. Если медленная, то и фпс можно уменьшить, нет смысла делать кучу вычислений, когда они не нужны. В этом случае может как раз сетТаймаут зайти, если колбэк быстрый. Или requestAnimationFrame с проверкой времени прошедшей с предыдущего вызова и запуском обработки реже чем вызывается сам колбек.
По поводу "с сетТаймаут - Отрисовка. Столько шагов сколько успеет..." Нет, с сетТаймаут так - вызов сетТаймаут с передачей с него колбек функции создает новый макротакс. Затем получение результатов всех зарезолвившихся микротасков. Затем отрисовка. ... Вызов колбек функции созданного ранее макротаска. Пока эта функция не завершится на следущий шаг не перейти. Если колбэк выполнняется 1 минуту, то на 1 минуту интерфейс зависнет. когда завершится колбэк сетТаймаута сработают по очереди обработчики для получения результатов работы микротасков. После этого произойдет следующая отрисовка.
@@EasyITChannel есть нюанс. setTimeout тоже неточная функция и также имеет idle состояния как раз когда, например, вкладка неактивна и др., именно поэтому таймеры на setTimeout и setInterval - это неточные функции, они не подойдут для того же приложения "таймер", например, там требуется делать компенсацию задержки из-за неточности.
Добрый день :) Вы не внимательно смотрели видео, услышали что-то что рушит уже привычную картину мира и сразу спорить. Сначала вызывается обработчик завершенного к данному моменту макро задачи, затем вызываются все ОБРАБОТЧИКИ завершенных к данному моменту микрозадач. Так происходит потому, что обработчики микрозадач вызываются тогда, когда сам промис УЖЕ зарезолвился, т.е. основная длительная работа сделана и нужно только забрать готовый результат. Функция тела промиса выполняется в отдельном потоке параллельно, но забрать результаты работы промиса можно только в основном потоке поэтому так и сделано. Толку с того, что промис выполнился супер быстро, если к Вам данные попадут только в тот момент, когда вызовется обработчик? В данном видео речь идет как раз об обработчиках, потому что это единственное с чем разработчик может взаимодействовать. Поэтому да, так и будет, если предположить, что время выполнения сет таймаута и тела промиса завершатся одновременно, то сначала сработает функция переданная в таймаут, а уже потом функция переданная в then. Верить мне на слово не нужно, именно поэтому тут кроме разговоров о высоком есть еще код.
Хорошо, выполняться они могут и одновременно, а как вывестись результат сеттаймаута может быстрее, если он сперва смотрит пуста ли очередь микрозадач,и если да, то закинет макро. Или в ответе имелось в виду, что сет сработает до того, как в очередь микротасок промис попадёт?
Нет, Вы не правы. Работает мэйн луп не так. 1. Проверяет есть ли в очереди макротаск, если есть выполняет его. На время выполения маротаска мэйн луп как бы зависает, не будет в том числе и перерисовки UI, не будет вызовов обработчиков промисов. Это легко проверить. 2. После завершения макротаска проверяет есть ли УЖЕ РАЗЕРЗОЛВЛЕННЫЕ (не находящиевся в очереди, нет!!!) микротаски, если есть - выполняет обработчики их всех. Обычно это быстрое действие, т.к. работа микротасков сделана в бэкграунде и нужно толко забрать результат. 3. Выполняется перерисовка UI 4. Переход к пункту 1. Есть подозрение, что Вы знаете какой-то другой язык кроме JS, в котором есть реализация мультипоточности поэтому происходит искажение восприятия JS. Напомню - JS язык однопоточный. Да у него есть механизмы типа промисов и сервис воркеров, но на общую картинку (см. main loop) это влияет не очень сильно. Все что написано выше проверено опытным путем на практике, если посмотреть это видео, там как раз об этом. Не хочу Вас обидеть, но в этом заблуждении Вы далеко не одиноки. Меня удивляет только то, почему так много разработчиков верят в то, что легко опровергается на практике.
Спасибо, очень полезное видео Не хочу показаться глупым, но хочу задать вопросы 1. Функции которые не являются ни микрозадачами, ни макрозадачами мы называем синхронными? 2. Если js встречает сначала макрозадачу тайм-аут 0, а после микро - промис. Сначала произойдет микрозадача, а потом макро. Вот для каких случаев верно утверждение из видео, что выполняется одна макрозадача, а после все микрозадачи? Потому что сначала сработает промис и потом тайм-аут. И до этого видео я уверенно так и думал, приоритет - микрозадачи, после макро
Вопросы очень правильные. Спасибо. 1. Любая синхронная функция вызывается в рамках или микрозадачи или макрозадачи. Обычно макро. Любой скрипт является макрозадачей, любая функция обработчик события тоже. Веб воркеры тут не рассматриваем, по этой теме будет отдельное видео. 2. Макро задача размещается в момент вызова функции таймаута, не в момент создания таймаута. Аналогично промис размещает обработчик в очереди микрозадач в момент срабатывания метода then, catch, finally и других. Не в момент создания промиса. Вот и получается. Наш код выполняется в какой-то макрозадаче (даже если в теге что-то написать, то это будет макрозадача) и в этой макрозадаче мы создаем еще одну макрозадачу через таймаут с задержкой 0 (на самом деле это будет в районе 4мсек) она попадает в очередь, но вызовется только после ближайшего перерендера. Это если в очереди других макрозадач нет. Когда завершится текущая макрозадача, то JS начнет извлекать все доступные в данный момент микро задачи, все что есть в очереди, и выполнять по очереди. После этого произойдет перерендер и уже потом JS возьмет следующую макрозадачу из очереди макрозадач (ту что мы создали с таймаутом 0). Вобщем если мы создаем макрозадачу через setTimeout, то функция обратного вызова сработает как минимум после ближайшего перерендера. Если же мы создаем уже зарезолвленный пропис - то он попадет в очередь микрозадач уже во время работы текущей задачи и после ее завершения будет обработан.
@@EasyITChannel То есть ,все функции задекларированные в коде не беря в расчет setTimote и промисы , являются макрозадачами , а то что внутри выполнятеся это микро - задачи? Типа doit =(text) => console.log(text) doit - макро-таска, а console.log(text) - микро-таска, а если поместить в setTimote doit =(text) => setTimote () , то setTimote уже будет -макро-таской помещенной в очередь-вызовов и выполнится после предыдущей макро-таски. Я правильно понял?
Это очень смешное утверждение. Примерно как - надпись на заборе это не забор. Вам нужно для начала научиться слушать и понимать услышанное. При необходимости пересмотреть несколько раз, а уже потом высказывать умные мысли. Конечно, Вы легко сможете указать место, где автор сказал, что консоль лог это макротаск. :)