the role of Event Loop, Event Bus, promises and async
Promises. Promises provide an improved way to handle asynchronous operations and manage their status (completed, rejected, pending). An example of using promises to load data asynchronously
function fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(response => response.json())
.then(data => resolve(data))
.catch(error => reject(error));
});
}
fetchData('https://api.example.com/data')
.then(data => console.log('Data loaded successfully:', data))
.catch(error => console.error('Error loading data:', error));
Async/Await. The async/await keywords provide syntactic sugar for working with promises, making the code more readable. Example of using async/await:
async function fetchData(url) { try { let response = await fetch(url); let data = await response.json(); return data; } catch (error) { throw new Error('Error loading data'); } } async function loadData() { try { let data = await fetchData('https://api.example.com/data'); console.log('Data loaded successfully:', data); } catch (error) { console.error('Error loading data:', error); } } loadData();
What are Event Loop and Event Bus
Event Loop
Event Loop is a mechanism present in JavaScript runtimes such as the browser and Node.js framework that allows for the processing of asynchronous operations and events. It supports a single-threaded code execution model while still ensuring the responsiveness of the application. Event Loop monitors the call stack and event queue.
Example:
console.log('Start');
setTimeout(function () {
console.log('Timeout 1');
}, 2000);
setTimeout(function () {
console.log('Timeout 2');
}, 1000);
console.log('End');
This example will print “Start” first, then “End”. However, the functions inside
setTimeout
will not be executed immediately. They will be added to the event queue after the specified time has elapsed (in this case, after 2 and 1 second, respectively). Once the call stack is free, Event Loop will add functions from the event queue to the stack and they will be executed.
Event Bus
An Event Bus is a mechanism in programming that allows various components or modules to communicate with each other by sending and listening to events. An Event Bus is often used to exchange messages between different parts of an application without explicitly linking those parts to each other.
Example:
// Пример простого Event Bus на основе событий в Node.js
const EventEmitter = require('events');
// Создаем экземпляр Event Bus
const eventBus = new EventEmitter();
// Компонент 1: слушает событие 'message'
eventBus.on('message', (data) => {
console.log('Component 1 received message:', data);
});
// Компонент 2: слушает событие 'message'
eventBus.on('message', (data) => {
console.log('Component 2 received message:', data);
});
// Компонент 3: отправляет событие 'message'
function sendMessage() {
eventBus.emit('message', 'Hello, Event Bus!');
}
// Вызываем функцию отправки сообщения
sendMessage();
In this example, components 1 and 2 listen to the 'message' event, and component 3 sends the event. When sendMessage
is called, both components 1 and 2 will receive a notification and print a message to the console. Event Bus allows you to conveniently organize interaction between components, even if they are located in different parts of the application.
For example, Vue.js 2 does not have an explicit Event Bus, but often uses a global Vue instance as a primitive Event Bus.
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 2 Event Bus Example</title>
</head>
<body>
<div id="app">
<child-component></child-component>
<another-child-component></another-child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script src="https://habr.com/ru/articles/801543/main.js"></script>
</body>
</html>
// main.js
Vue.component('child-component', {
template: '<div><button @click="sendMessage">Send Message</button></div>',
methods: {
sendMessage() {
this.$root.$emit('message', 'Hello from Child Component');
}
}
});
Vue.component('another-child-component', {
template: '<div>Another Child Component</div>',
created() {
this.$root.$on('message', (data) => {
console.log('Another Child Component received message:', data);
});
}
});
new Vue({
el: '#app',
});
This example creates a global Vue instance (new Vue
) in the root element of the application. Component child-component
contains a button that, when clicked, sends a message via $emit
to the global Event Bus (this.$root
). Component another-child-component
listens to this event through $on
while creating.
The application as a whole acts as an Event Bus, allowing components to communicate with each other by sending and listening to events. In a real project, it's better to use Vuex for state management, but for simple cases, Event Bus can be a handy tool.
Micro and macro tasks
Microtasks and macrotasks are parts of the asynchronous execution model in JavaScript and are associated with mechanisms such as Event Loop.
Macrotasks:
Definition: Macro tasks are larger tasks that are added to the Event Loop execution queue.
Usage: Tasks such as user input processing, script execution, asynchronous I/O (input/output) operations, timers (setTimeout, setInterval), animation requests and DOM events.
console.log('Start'); setTimeout(function () { console.log('Timeout (Macrotask)'); }, 0); console.log('End');
In this example, the function passed to
setTimeout
, is a macrotask. It will be executed after the main code is executed, even if the timer is set to 0 milliseconds.Microtasks:
Definition: Microtasks are smaller, faster tasks that are processed at the end of each macrotask in the current call stack.
Usage: Promises (then, catch, finally), async/await operator.
console.log('Start'); Promise.resolve().then(function () { console.log('Promise (Microtask)'); }); console.log('End');
Here's the function passed to
.then()
promise is a microtask. It will be executed after the main code and any macro tasks, but before the events in the event queue.
Execution order:
The main code is executed.
Microtasks (if any) from the current call stack are executed.
The macrotask (the first one in the queue) is running.
Repeat steps 2-3 until the macrotask queue is empty.
Promises
Promises are a powerful mechanism in JavaScript for managing asynchronous operations. They are used to handle results or errors that may occur in the future after an asynchronous task has completed. Promises provide a readable and convenient syntax for working with asynchronous operations.
Basic properties and methods of promises
new Promise(executor)
: Creates a new promise object.executor
is a function that takes two arguments: a functionresolve
and functionreject
. They are used to complete the promise successfully (resolve
) or with an error (reject
).let promise = new Promise((resolve, reject) => { // асинхронная операция let success = true; if (success) { resolve('Успех!'); } else { reject('Ошибка!'); } });
promise.then(onFulfilled, onRejected)
: Methodthen
adds handlers for successful completion (onFulfilled
) or errors (onRejected
). Each of them is a function that accepts a result or an error respectively.promise.then( result => console.log(result), error => console.error(error) );
promise.catch(onRejected)
: Methodcatch
used for error handling, similar to the second argument inthen
.promise.catch(error => console.error(error));
Promise.all(iterable)
: Returns a promise that is fulfilled when all promises in the passed array or iterable complete, or fail with the first error.let promise1 = Promise.resolve(1); let promise2 = new Promise(resolve => setTimeout(() => resolve(2), 1000)); let promise3 = Promise.reject('Ошибка'); Promise.all([promise1, promise2]) .then(values => console.log(values)) .catch(error => console.error(error)); // будет вызвано, если один из промисов отклонится
Promise.race(iterable)
: Returns a promise that is fulfilled or rejected according to how the first promise in the passed array or iterable ends.let promise1 = new Promise(resolve => setTimeout(() => resolve('Winner'), 1000)); let promise2 = new Promise(resolve => setTimeout(() => resolve('Loser'), 2000)); Promise.race([promise1, promise2]) .then(winner => console.log(winner)) // 'Winner' .catch(error => console.error(error));
An example of using promises
function fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(response => response.json())
.then(data => resolve(data))
.catch(error => reject(error));
});
}
fetchData('https://api.example.com/data')
.then(data => console.log('Data loaded successfully:', data))
.catch(error => console.error('Error loading data:', error));
This example demonstrates the use of a promise to load data from the server asynchronously. Method fetchData
returns a promise that resolves successfully when the data is loaded successfully, and rejects if there is an error. then
And catch
are used to handle success and error respectively.
Fetch API
The Fetch API is an interface for sending and receiving HTTP requests. It provides a more flexible and powerful way to handle network requests compared to the legacy XMLHttpRequest. The Fetch API is based on promises, which makes it convenient for asynchronous programming.
Main features of Fetch API:
Ease of use. The Fetch API provides a simple and easy to use syntax based on promises. This makes the code more readable and understandable.
fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error));
Support for headers and methods. The Fetch API allows you to easily manage HTTP request headers and methods.
fetch('https://api.example.com/data', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token123' }, body: JSON.stringify({ key: 'value' }) }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error));
Streams. The Fetch API supports data streaming, which is useful when working with large amounts of data, such as file downloads or streaming reads.
Method
Response
: An objectResponse
returned by the methodfetch
provides many methods for working with responses, such asjson()
,text()
,blob()
and others.fetch('https://api.example.com/data') .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => console.log(data)) .catch(error => console.error('Fetch error:', error));
Cancel requests: The Fetch API does not have built-in support for canceling requests, but you can use third-party libraries or create your own cancellation mechanism based on component lifecycle control (in the case of web applications).
Fetch API usage example
// Отправка GET-запроса
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log('Data loaded successfully:', data))
.catch(error => console.error('Error loading data:', error));
// Отправка POST-запроса с данными
fetch('https://api.example.com/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => console.log('Data submitted successfully:', data))
.catch(error => console.error('Error submitting data:', error));
This example demonstrates sending GET and POST requests using the Fetch API to load and post data to the server.
Syntactic Sugar – Async and Await
Syntactic sugar in programming is a convenient and more readable syntax for performing certain operations. In the context of JavaScript, keywords async
And await
provide syntactic sugar for working with promises, which makes the code more concise and easier to understand.
async
Keyword async
used before a function to indicate that the function always returns a promise. The promise will be resolved with the result of the function execution or rejected with an error.
Example without async
:
function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve('Data loaded successfully');
}, 2000);
});
}
Example with async
:
async function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve('Data loaded successfully');
}, 2000);
});
}
await
Keyword await
used inside a function declared using async
, to wait for the promise to be fulfilled. It pauses the function until the promise is resolved or rejected.
Example without await
:
function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve('Data loaded successfully');
}, 2000);
});
}
function processData() {
fetchData().then(data => {
console.log(data);
});
}
processData();
Example with await
:
async function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve('Data loaded successfully');
}, 2000);
});
}
async function processData() {
const data = await fetchData();
console.log(data);
}
processData();
Usage await
makes the code more linear and similar to synchronous code, making it easier to read and understand.
Example of using async and await together:
async function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve('Data loaded successfully');
}, 2000);
});
}
async function processData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
processData();
Here we declare an asynchronous function processData
we use await
to wait for the fulfillment of a promise from fetchData
and use the block try/catch
to handle possible errors. This makes the code more expressive and easier to handle asynchronous operations.