JavaScript ES2018
Rest/spread for objects and async iteration
🔄 What is ES2018?
ES2018 brought rest/spread properties for objects, async iteration, and improved regular expressions. These features made object manipulation and asynchronous programming even more powerful.
// ES2018 introduced object rest/spread
const person = { name: "John", age: 30, city: "NYC" };
const { name, ...rest } = person;
console.log(name); // "John"
console.log(rest); // { age: 30, city: "NYC" }
// Object spread
const newPerson = { ...person, job: "Developer" };
console.log(newPerson);
Output:
John
{ age: 30, city: "NYC" }
{ name: "John", age: 30, city: "NYC", job: "Developer" }
ES2018 Features
Object Rest/Spread
Rest and spread for objects
const {a, ...rest} = obj;
const newObj = {...obj, b: 2};
Async Iteration
for-await-of loops
for await (const item of asyncIterable) {
console.log(item);
}
Promise.finally()
Always execute code after promise
promise
.then(result => {})
.finally(() => cleanup());
RegExp Improvements
Named capture groups & more
const regex = /(?\d{4})/;
const match = regex.exec("2023");
🔹 Object Rest and Spread
Extract and combine object properties easily:
// Object Rest - extract remaining properties
const user = {
id: 1,
name: "Alice",
email: "[email protected]",
age: 28,
city: "Boston"
};
// Extract specific properties, rest goes into 'others'
const { id, name, ...others } = user;
console.log(id); // 1
console.log(name); // "Alice"
console.log(others); // { email: "[email protected]", age: 28, city: "Boston" }
// Object Spread - combine objects
const basicInfo = { name: "Bob", age: 25 };
const contactInfo = { email: "[email protected]", phone: "123-456-7890" };
const address = { city: "Seattle", state: "WA" };
// Combine all objects
const completeProfile = {
...basicInfo,
...contactInfo,
...address,
status: "active"
};
console.log(completeProfile);
// Override properties
const originalSettings = { theme: "light", fontSize: 14, autoSave: true };
const userPreferences = { theme: "dark", fontSize: 16 };
const finalSettings = { ...originalSettings, ...userPreferences };
console.log(finalSettings); // theme and fontSize are overridden
Output:
1
Alice
{ email: "[email protected]", age: 28, city: "Boston" }
{ name: "Bob", age: 25, email: "[email protected]", phone: "123-456-7890", city: "Seattle", state: "WA", status: "active" }
{ theme: "dark", fontSize: 16, autoSave: true }
🔹 Async Iteration (for-await-of)
Iterate over async data sources:
// Create an async generator
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
// Use for-await-of to iterate
async function processAsyncData() {
console.log("Starting async iteration...");
for await (const value of asyncGenerator()) {
console.log("Received:", value);
}
console.log("Async iteration complete!");
}
processAsyncData();
// Real-world example: Processing API responses
async function* fetchUserData(userIds) {
for (const id of userIds) {
// Simulate API call
const response = await new Promise(resolve =>
setTimeout(() => resolve({ id, name: `User${id}` }), 100)
);
yield response;
}
}
async function displayUsers() {
const userIds = [1, 2, 3, 4, 5];
console.log("Fetching users...");
for await (const user of fetchUserData(userIds)) {
console.log(`Loaded: ${user.name} (ID: ${user.id})`);
}
console.log("All users loaded!");
}
displayUsers();
Output:
Starting async iteration...
Received: 1
Received: 2
Received: 3
Async iteration complete!
Fetching users...
Loaded: User1 (ID: 1)
Loaded: User2 (ID: 2)
Loaded: User3 (ID: 3)
Loaded: User4 (ID: 4)
Loaded: User5 (ID: 5)
All users loaded!
🔹 Promise.finally()
Execute code regardless of promise outcome:
// Promise.finally() always runs
function fetchData(shouldSucceed) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldSucceed) {
resolve("Data loaded successfully!");
} else {
reject(new Error("Failed to load data"));
}
}, 1000);
});
}
// Example 1: Successful promise
fetchData(true)
.then(result => {
console.log("Success:", result);
})
.catch(error => {
console.log("Error:", error.message);
})
.finally(() => {
console.log("Cleanup: Hiding loading spinner");
});
// Example 2: Failed promise
fetchData(false)
.then(result => {
console.log("Success:", result);
})
.catch(error => {
console.log("Error:", error.message);
})
.finally(() => {
console.log("Cleanup: Hiding loading spinner");
});
// Practical example: File upload with loading state
async function uploadFile(file) {
const loadingElement = document.getElementById('loading');
try {
// Show loading
if (loadingElement) loadingElement.style.display = 'block';
// Simulate file upload
const result = await new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve("Upload successful!") : reject(new Error("Upload failed"));
}, 2000);
});
console.log(result);
return result;
} catch (error) {
console.error("Upload error:", error.message);
throw error;
} finally {
// Always hide loading, regardless of success or failure
if (loadingElement) loadingElement.style.display = 'none';
console.log("Loading state cleared");
}
}
// Usage
uploadFile("document.pdf");
Output:
Success: Data loaded successfully!
Cleanup: Hiding loading spinner
Error: Failed to load data
Cleanup: Hiding loading spinner
Loading state cleared
(Upload result varies)
🔹 RegExp Named Capture Groups
Give names to regex capture groups for better readability:
// Old way with numbered groups
const dateRegex = /(\d{4})-(\d{2})-(\d{2})/;
const dateMatch = dateRegex.exec("2023-12-25");
console.log("Year:", dateMatch[1]); // Hard to remember which is which
console.log("Month:", dateMatch[2]);
console.log("Day:", dateMatch[3]);
// New way with named groups
const namedDateRegex = /(?\d{4})-(?\d{2})-(?\d{2})/;
const namedMatch = namedDateRegex.exec("2023-12-25");
console.log("Year:", namedMatch.groups.year); // Much clearer!
console.log("Month:", namedMatch.groups.month);
console.log("Day:", namedMatch.groups.day);
// Email validation with named groups
const emailRegex = /(?[a-zA-Z0-9._%+-]+)@(?[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/;
const emailMatch = emailRegex.exec("[email protected]");
if (emailMatch) {
console.log("Username:", emailMatch.groups.username);
console.log("Domain:", emailMatch.groups.domain);
}
// Phone number parsing
const phoneRegex = /(?\d{3})-(?\d{3})-(?\d{4})/;
const phoneMatch = phoneRegex.exec("555-123-4567");
if (phoneMatch) {
const { area, exchange, number } = phoneMatch.groups;
console.log(`Area: ${area}, Exchange: ${exchange}, Number: ${number}`);
}
// URL parsing
const urlRegex = /(?https?):\/\/(?[^\/]+)(?\/.*)?/;
const urlMatch = urlRegex.exec("https://www.example.com/path/to/page");
if (urlMatch) {
console.log("Protocol:", urlMatch.groups.protocol);
console.log("Domain:", urlMatch.groups.domain);
console.log("Path:", urlMatch.groups.path || "/");
}
Output:
Year: 2023
Month: 12
Day: 25
Year: 2023
Month: 12
Day: 25
Username: john.doe
Domain: example.com
Area: 555, Exchange: 123, Number: 4567
Protocol: https
Domain: www.example.com
Path: /path/to/page