Understanding `new Promise` in JavaScript
The Promise constructor takes a single parameter, an executor
function.
JavaScript then executes your executor
function with 2
arguments: resolve()
and reject()
.
function executor(resolve, reject) {
typeof resolve; // 'function'
typeof reject; // 'function'
}
new Promise(executor);
Your executor function is responsible for calling resolve()
to mark
the promise as fulfilled (successful) or rejected (failed).
const success = new Promise(function executor(resolve) {
resolve('OK');
});
const fail = new Promise(function executor(resolve, reject) {
reject(new Error('Oops'));
});
const str = await success;
str; // 'OK'
const err = await fail.catch(err => err);
err.message; // Oops
You can register an event listener for when a promise is fulfilled
or rejected using the then()
function.
const success = new Promise(function executor(resolve) {
setTimeout(() => resolve('OK'), 100);
});
const start = Date.now();
return success.then(function onFulfilled(str) {
str; // 'OK'
const elapsed = Date.now() - start;
elapsed; // Approximately 100
});
Using Promises for Timeouts
You don't need to create new promises very often. Usually, libraries like
Axios or Mongoose create promises internally and return them, so you can use
then()
or await
.
However, not all APIs support promises. For example, the setTimeout()
function only accepts callbacks.
In order to create a promise that waits for 100ms before resolving, you
should wrap a setTimeout()
call in a new Promise
:
async function test() {
// Pause the async function for 100ms
await new Promise(resolve => setTimeout(resolve, 100));
return 'OK';
}
const start = Date.now();
await test();
const elapsed = Date.now() - start;
elapsed; // Approximately 100
Wrapping Node-Style Callbacks
Some async Node.js APIs, like fs.readFile()
, don't return promises.
You also need to wrap fs.readFile()
in a new Promise
in order
to use it with async/await.
Make sure you handle errors! Node-style callbacks take 2 parameters: an error
and a result
.
If error
is not nullish, you should reject the promise.
const fs = require('fs');
const p = new Promise(function executor(resolve, reject) {
fs.readFile('./package.json', (error, result) => {
if (error != null) {
// Note the early return!
return reject(error);
}
resolve(result);
});
});
const pkg = JSON.parse(await p);
pkg.name; // 'masteringjs.io'
Async Executor Functions
A common mistake is making the executor an async function.
const p = new Promise(async function executor(resolve, reject) {
await new Promise(resolve => setTimeout(resolve, 100));
resolve('OK');
});
const str = await p;
The above code works fine, but it creates an unnecessary promise (remember that async functions always return a promise!) and looks clumsy. Since async functions always return promises, you can always replace an async executor function with a vanilla async function call:
async function test() {
await new Promise(resolve => setTimeout(resolve, 100));
return 'OK';
}
const p = test();
The key takeaway is that you should never make an executor function async. There's no need.