«Як JavaScript може бути асинхронним і однопоточним одночасно?». Якщо коротко, то JavaScript однопоточний, а асинхронна поведінка не є частиною самої мови; замість цього вона побудована в браузері (або середовищі програмування) і доступна через браузерне API.
Тепер подивимося на довгу відповідь.
Базова архітектура
- Heap - об'єкти зібрані в купу, яка є ні що інше, як назва для найменш структурованої частини пам'яті.
- Stack - репрезентація єдиного потоку виконання JavaScript-коду. Виклики функцій поміщаються в стек (про це нижче).
- Browser or Web API's - вбудовані в браузер і здатні надавати дані з браузера і навколишнього комп'ютерного середовища і давати можливість виконувати з ними корисні і складні речі. Вони не є частиною мови JavaScript, але вони побудовані на його основі. Наприклад Geolocation API надає доступ до декількох простих конструкцій JavaScript, які використовуються для отримання даних про місцезнаходження, так що ви можете, скажімо, відобразити своє місце розташування на Google Map. У фоновому режимі браузер використовує низькорівневий код (наприклад C ++) для зв'язку з обладнанням GPS пристрою (або будь-яким іншим, доступним для визначення даних про місцезнаходження), отримання даних про місцезнаходження і повернення їх в середовище браузера для використання в вашому коді. Але знову, ця складність абстрагована від вас за допомогою API.
Приклад коду 1: Інтрига
function main ( ) {
console . log ( 'A' )
setTimeout ( function exec ( ) {
console . log ( 'B' )
} , 0 )
console . log ( 'C' )
}
main ( )
// Output
// A
// C
// B
Тут ми бачимо функцію main, що включає в себе два console.log, виводять в консоль A і C. Між ними знаходиться setTimeout, виклик якого виведе в консоль B після очікування в 0 секунд.
Ось що відбувається всередині під час виконання
1. Виклик функції main спочатку помістить її в стек (в якості першого елемента (frame)). Потім браузер помістить в стек перший вираз функції main, який представляє собою console.log(‘A’). Цей вислів виконується і, після завершення, видаляється з стека. Буква A виводиться в консоль.
2. Наступний вираз ( setTimeout() з коллбеком exec() і часом очікування в 0 секунд) поміщається в стек викликів і виконання починається. Функція setTimeout використовує API браузера для затримки виклику наданої функції. Елемент (frame) видаляється з стека відразу після завершення передачі таймера браузерних API.
3. console.log(‘C’) поміщається в стек, поки в браузері запускається таймер для виклику функції exec(). У цьому конкретному випадку, оскільки час очікування становить 0 секунд, коллбек (функція exec()) буде поміщений в message queue (черга повідомлень), відразу після того як браузер його отримає (в ідеалі).
4. Після виконання останнього виразу функції main, елемент main видаляється з стека викликів (call stack), залишаючи його порожнім. Стек викликів повинен бути порожнім, для того щоб браузер помістив в нього елемент з message queue. Саме з цієї причини навіть якщо в setTimeout зазначений час очікування в 0 секунд, функція exec() не виконується, поки не закінчиться виконання всіх елементів в стеку викликів.
5. Тепер функція exec() поміщається в стек викликів і виконується. Буква C виводиться в консоль. Ось він - цикл подій (EventLoop) JavaScript.
Таким чином аргумент delay в setTimeout function, delayTime) не означає точний час затримки, після якого функція виконається. Він означає мінімальний час очікування, після якого в який-небудь момент часу, функція буде викликана.
Приклад коду 2: Більш глибоке розуміння
function main ( ) {
console . log ( 'A' )
setTimeout ( function exec ( ) {
console . log ( 'B' )
} , 0 )
runWhileLoopForNSeconds ( 3 )
console . log ( 'C' )
}
main ( )
function runWhileLoopForNSeconds ( sec ) {
let start = Date . now ( ) ,
now = start
while ( now - start < sec * 1000 ) {
now = Date . now ( )
}
}
// Output
// A
// C
// B
Функція runWhileLoopForNSeconds() робить саме те, що відображено в її назві. Вона постійно перевіряє, чи пройшло з часу її виклику тоі кількість секунд, які передані аргументом. Головне, що потрібно пам'ятати - що цикл while є блокуючим виразом, і це означає, що його виконання відбувається в стеці викликів і не використовує браузерні API. Таким чином він блокує всі наступні вирази, поки не виконається до кінця.
У коді вище, навіть не дивлячись на те, що setTimeout має затримку в 0 секунд і цикл while виконується 3 секунди, функція exec() застрягне в черзі повідомлень. Цикл while буде виконуватися в стеці викликів (в якому один потік), поки не пройде 3 секунди. І тільки після того, як стек викликів спорожніє, функція exec() буде поміщена в стек і виконана.
Таким чином аргумент delay в setTimeout() не гарантує початку виконання після завершення зазначеної затримки. Він є мінімальним часом затримки.
0 комментариев
Добавить комментарий