Author: zeel
Message:
JS offers two ways of doing this: Promise.then()
uses the callback style syntax. Much like an event handler, you pass then()
a function, and JS will call that function only once the Promise is fulfilled - it won’t call prepGameSession()
until the package with the book in it is delivered. Often, multiple Promises are chained together, with each .then()
returning another Promise.
function beGM() {
orderBook() // The promise fullfills when the order is delivered
.then((book) => prepGameSession(book))
.then((session) => playTTRPG(session));
}
The other, somewhat more straightforward but often more mysterious method is async
and await
. The more important keyword here is await
it means “stop here until the next thing is done.” When you use this method, it’s a bit more clear what you are doing, though it introduces a quirk: You can’t await
if your function isn’t async
, and the return value of an async
function is always a Promise - this can cause some confusion, and a cascade of refactoring as many methods get converted to async
in an attempt to make things work as expected.
async function beGM() {
const book = await orderBook();
const session = await prepGameSession(book);
playTTRPG(session);
}
When creating a module, you often need to interact with the server. Just like ordering a book, when you interact with the server you might need to wait for a response. If you don’t, you might end up acting on data that doesn’t exist, or hasn’t changed, or doesn’t make sense. Or you might accidentally tell the server to update two things at the same time, which it doesn’t handle very well.
Another thing to watch out for, are loops.
JS has some nifty loops attached to the Array type, things like forEach
and map
and reduce
. But these methods can cause some issues with Promises.
Imagine you want to start reading a new series of books. But you are unsure if you will like them, and you are on a budget. You want to order them, one at a time, and not order the next book until you finish reading the one that comes before.
If you do something like this:
for (let book of books) {
let package = await order(book);
let opinion = await read(package.book);
if (!opinion.isGood()) break;
}
It works great, you will order a book, read it, and if you like it you will order the next. If you ever dislike one, you will stop ordering and exit the loop.
But what about forEach
? :
books.forEach(async (book) => {
let package = await order(book);
let opinion = await read(package.book);
if (!opinion.isGood()) /* Uh, you can't actually use break in one of these... */;
});
Looks the same right? But notice, the arrow function passed to forEach
it has to be declared as async
. Funny thing is, all async
functions return Promises. And they return immediately. The forEach
method of Array doesn’t know this, and it isn’t an async
method - it won’t await each iteration of the loop! It will instead call the function once for each item no matter what! Suddenly it orders the entire series of books, all at once! And you get a really nasty credit card bill.
(Imagine you did this for Discworld, over 40 books at about $10 a pop! Though luckily it’s Pratchett, so no worries about disliking them.)