JavaScript Promises Tutorial - Section 5: Advanced Patterns
Reusable utilities make your code:
Create a Promise-based delay:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Usage
await delay(1000); // Wait 1 second
console.log('1 second later');
With value:
function delay(ms, value) {
return new Promise(resolve => {
setTimeout(() => resolve(value), ms);
});
}
const result = await delay(1000, 'Hello');
console.log(result); // 'Hello' after 1 second
Add timeout to any Promise:
function timeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), ms);
})
]);
}
// Usage
try {
const data = await timeout(fetchData(), 5000);
console.log(data);
} catch (error) {
console.error('Request timed out');
}
Retry failed operations:
async function retry(fn, retries = 3, delay = 1000) {
try {
return await fn();
} catch (error) {
if (retries <= 0) {
throw error;
}
console.log(`Retrying... (${retries} attempts left)`);
await new Promise(resolve => setTimeout(resolve, delay));
return retry(fn, retries - 1, delay);
}
}
// Usage
const data = await retry(() => fetchData(), 3, 1000);
Convert callback-based functions to Promises:
function promisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
}
// Usage
const fs = require('fs');
const readFileAsync = promisify(fs.readFile);
const content = await readFileAsync('file.txt', 'utf8');
Run Promises one after another:
async function sequential(tasks) {
const results = [];
for (const task of tasks) {
const result = await task();
results.push(result);
}
return results;
}
// Usage
const results = await sequential([
() => fetchUser(1),
() => fetchUser(2),
() => fetchUser(3)
]);
Control concurrency:
async function parallelLimit(tasks, limit) {
const results = [];
const executing = [];
for (const task of tasks) {
const promise = Promise.resolve().then(() => task());
results.push(promise);
if (limit <= tasks.length) {
const e = promise.then(() => {
executing.splice(executing.indexOf(e), 1);
});
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
// Usage: Run max 3 tasks at once
const results = await parallelLimit(tasks, 3);
Transform array items asynchronously:
async function mapAsync(array, asyncFn) {
return Promise.all(array.map(asyncFn));
}
// Usage
const userIds = [1, 2, 3, 4, 5];
const users = await mapAsync(userIds, id => fetchUser(id));
Filter array with async predicate:
async function filterAsync(array, asyncPredicate) {
const results = await Promise.all(
array.map(async item => ({
item,
pass: await asyncPredicate(item)
}))
);
return results
.filter(result => result.pass)
.map(result => result.item);
}
// Usage
const activeUsers = await filterAsync(users,
user => checkIfActive(user.id)
);
// promiseUtils.js
const PromiseUtils = {
delay: (ms, value) => new Promise(resolve =>
setTimeout(() => resolve(value), ms)
),
timeout: (promise, ms) => Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
)
]),
retry: async (fn, retries = 3, delay = 1000) => {
try {
return await fn();
} catch (error) {
if (retries <= 0) throw error;
await PromiseUtils.delay(delay);
return PromiseUtils.retry(fn, retries - 1, delay * 2);
}
},
sequential: async (tasks) => {
const results = [];
for (const task of tasks) {
results.push(await task());
}
return results;
},
mapAsync: (array, fn) => Promise.all(array.map(fn)),
filterAsync: async (array, predicate) => {
const results = await Promise.all(
array.map(async item => ({
item,
pass: await predicate(item)
}))
);
return results.filter(r => r.pass).map(r => r.item);
}
};
export default PromiseUtils;
class APIClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
// Add timeout
const fetchWithTimeout = PromiseUtils.timeout(
fetch(url, options),
5000
);
// Add retry logic
return PromiseUtils.retry(
async () => {
const response = await fetchWithTimeout;
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
},
3,
1000
);
}
async batchGet(endpoints) {
return PromiseUtils.mapAsync(
endpoints,
endpoint => this.request(endpoint)
);
}
}
// Usage
const api = new APIClient('https://api.example.com');
const data = await api.batchGet(['/users', '/posts', '/comments']);
delay() creates Promise-based timeoutstimeout() adds time limits to Promisesretry() implements automatic retry logicpromisify() converts callbacks to PromisesContinue exploring advanced patterns or jump to testing async code in Section 7!