Concurrency
Prefer promises vs callbacks
Callbacks aren't clean, and they cause excessive amounts of nesting (the callback hell).
There are utilities that transform existing functions using the callback style to a version that returns promises
(for Node.js see util.promisify
, for general purpose see pify, es6-promisify)
Bad:
import { get } from 'request';
import { writeFile } from 'fs';
function downloadPage(url: string, saveTo: string, callback: (error: Error, content?: string) => void) {
get(url, (error, response) => {
if (error) {
callback(error);
} else {
writeFile(saveTo, response.body, (error) => {
if (error) {
callback(error);
} else {
callback(null, response.body);
}
});
}
});
}
downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html', (error, content) => {
if (error) {
console.error(error);
} else {
console.log(content);
}
});
Good:
import { get } from 'request';
import { writeFile } from 'fs';
import { promisify } from 'util';
const write = promisify(writeFile);
function downloadPage(url: string, saveTo: string): Promise<string> {
return get(url)
.then(response => write(saveTo, response));
}
downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html')
.then(content => console.log(content))
.catch(error => console.error(error));
Promises supports a few helper methods that help make code more concise:
Pattern | Description |
---|---|
Promise.resolve(value) |
Convert a value into a resolved promise. |
Promise.reject(error) |
Convert an error into a rejected promise. |
Promise.all(promises) |
Returns a new promise which is fulfilled with an array of fulfillment values for the passed promises or rejects with the reason of the first promise that rejects. |
Promise.race(promises) |
Returns a new promise which is fulfilled/rejected with the result/error of the first settled promise from the array of passed promises. |
Promise.all
is especially useful when there is a need to run tasks in parallel. Promise.race
makes it easier to implement things like timeouts for promises.
Async/Await are even cleaner than Promises
With async
/await
syntax you can write code that is far cleaner and more understandable than chained promises. Within a function prefixed with async
keyword, you have a way to tell the JavaScript runtime to pause the execution of code on the await
keyword (when used on a promise).
Bad:
import { get } from 'request';
import { writeFile } from 'fs';
import { promisify } from 'util';
const write = util.promisify(writeFile);
function downloadPage(url: string, saveTo: string): Promise<string> {
return get(url).then(response => write(saveTo, response));
}
downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html')
.then(content => console.log(content))
.catch(error => console.error(error));
Good:
import { get } from 'request';
import { writeFile } from 'fs';
import { promisify } from 'util';
const write = promisify(writeFile);
async function downloadPage(url: string): Promise<string> {
const response = await get(url);
return response;
}
// somewhere in an async function
try {
const content = await downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
await write('article.html', content);
console.log(content);
} catch (error) {
console.error(error);
}