To make a brand-new promise to run code in parallel, simply use the Promise
constructor:
let myPromise = new Promise((resolve, reject) => {
// do things in a separate thread
if (thereWasAnError) {
reject(errorInfo);
} else {
resolve(results);
}
});
// do things in the main thread
When the promise has completed (ie., resolve()
has been called), you can use the results this way:
myPromise.then(results => {
// do thing with results
});
When the promise has an issue (ie., reject()
was called or an unhandled exception occured), you can use the error info this way:
myPromise.catch(errorInfo => {
// do thing with error
});
If you need to call more asynchronous code within a .then
, just return the resulting promise and handle the results in another then
:
myPromise.then(results => {
let anotherPromise = doAsyncThingWith(results);
return anotherPromise;
}).then(anotherResults => {
// do more things
});
At the end of it all, you can clean things up with finally
myPromise
.then(results => { })
.catch(err => { })
.finally(() => { });
You can also run multiple promises in parallel:
const totalPromise = Promise.all([promise1, promise2, promise3]);
totalPromise.then(([result1, result2, result3]) => { });
If your JS environment supports it, you can use async
and await
to handle promises:
let myPromise = doSomethingAsync();
try {
let results = await myPromise;
doMoreThings(results);
} catch (err) {
doErrorThings(err);
} finally {
cleanupThings();
}
This is more or less equivalent to the following:
let myPromise = doSomethingAsync();
myPromise.then(results => {
doMoreThings(results);
}).catch(err => {
doErrorThings(err);
}).finally(() => {
cleanupThings();
});
When using await
, it must be in an async
function (the reason why below), which can be declared number of ways:
async function myFunction1(x, y) { }
const myFunction2 = async function(x, y) { };
const myFunction3 = async (x, y) => { };
async
is just syntactic sugar that forces a function to return a promise, so the following
async function f(x) {
let results = await doThingsAsync(x);
return results;
}
is equivalent to
function f(x) {
let promise = doThingsAsync(x);
return promise;
}
NOTE: it turns out this might not actually be the case. Recently we've found uses of await
that block the EventLoop (and thus the entire application) that when replaced by simply returning the promise, run just fine.
Thus, when we call an async
function, we need to either await
the results (requiring the caller to be async
as well)
const fResultPromise = f(123);
const fResult = await fResultPromise;
or put a then
on the end:
const fResultPromise = f(123);
fResultPromise.then(fResult => { });
Remember, it's just syntactic sugar.
Sometimes NodeJS libraries do this:
let thing = doThingsAsync(args);
thing.on("stuff", data => { });
thing.on("done", result => { });
thing.on("error", err => { });
We can deal with this simply by wrapping it in a promise and resolving/rejecting in the appropriate callbacks:
let thingPromise = new Promise((resolve, reject) => {
let thing = doThingsAsync(args);
thing.on("stuff", data => { });
thing.on("done", result => resolve(result));
thing.on("error", err => reject(err));
});
thingPromise.then(result => { }).catch(err => { });
Wrapping it all up in a promise should make it much easier to reason about (and compose with other promises) later.
You can do this with Mongo-like callbacks too:
new Promise((resolve, reject) => {
MongoClient.connect(url, (err, db) => {
if (err) reject(err);
resolve(db);
});
}).then(db => new Promise((resolve, reject) => {
db.collection("stuff").find({ _id: 123 }).toArray((err, result) => {
if (err) reject(err);
resolve(db, result);
});
})).then((db, result) => new Promise((resolve, reject) => {
db.collection("stuff").find({ _id: result[0].foreignID }).toArray((err, result) => {
if (err) reject(err);
resolve(db, result);
});
})).then((db, result) => {
db.close();
writeToHttpResponse(result);
});
Or, you can use await
in an async
function:
let db = await new Promise((resolve, reject) => {
MongoClient.connect(url, (err, db) => {
if (err) reject(err);
resolve(db);
});
});
let result1 = await new Promise((resolve, reject) => {
db.collection("stuff").find({ _id: 123 }).toArray((err, result) => {
if (err) reject(err);
resolve(result);
});
});
let result2 = await new Promise((resolve, reject) => {
db.collection("stuff").find({ _id: result1[0].foreignID }).toArray((err, result) => {
if (err) reject(err);
resolve(result);
});
})
db.close();
writeToHttpResponse(result2);
...though you could probably find a wrapper around MongoDB that does all the promise-wrapping for you.