Trouble with resolving promises in sequence - javascript

I'm trying to resolve an array of promises in the build process of a NextJS app since I'm getting network errors when using Promise.all, but for some reason I'm having trouble resolving the promises.
This code works, but does not work with my build:
const activityPlacesPromises = activityLocationIDs.map((id) =>
this.get(`places/${id}`)
);
const activityPlaces = await Promise.all(activityPlacesPromises);
console.log(activityPlaces); // Returns the data correctly
This code does not work:
const activityPlaces = activityLocationIDs.reduce((promise, id) => {
return promise.then(() => this.get(`places/${id}`));
}, Promise.resolve());
console.log(activityPlaces); // Returns Promise { <pending> }
Why isn't the Promise.resolve() reduce function working?
PS: I am going off of this SO question: Resolve promises one after another (i.e. in sequence)?

activityPlace is still just a promise you will need to await
console.log(await activityPlaces);
Note that you're not doing anything with the result of each promise (aside from the last one)
Wouldn't it be way easier to just throw this in a regular for loop and await one by one? the reduce pattern is useful for cases where you don't have async/await, but you don't seem to have this limitation:
const results = [];
for(const id of activityLocationIDs) {
results.push(await this.get(`places/${id}`));
}
console.log(result);
The above code matches the behavior of your first sample.

It looks like you are trying to avoid using async/await, firstly perhaps you should catch any errors, to allow logging and execution to continue:
const activityPlaces = activityLocationIDs.reduce((promise, id) => {
return promise
.then(() => this.get(`places/${id}`))
.catch((err) => console.error(`Error getting place ID: ${id}`, err))
}, Promise.resolve());
activityPlaces.then(() => console.log(activityPlaces));
Also, consider that since your code is no longer using async, your build may not wait for the promises to resolve before ending.

Related

Incorporating async actions, promise.then() and recursive setTimeout whilst avoiding "deferred antipattern"

I have been reading up on methods to implement a polling function and found a great article on https://davidwalsh.name/javascript-polling. Now using a setTimeout rather than setInterval to poll makes a log of sense, especially with an API that I have no control over and has shown to have varying response times.
So I tried to implement such a solution in my own code in order to challenge my understanding of callbacks, promises and the event loop. I have followed guidance outlined in the post to avoid any anti-patterns Is this a "Deferred Antipattern"? and to ensure promise resolution before a .then() promise resolve before inner promise resolved and this is where I am getting stuck. I have put some code together to simulate the scenario so I can highlight the issues.
My hypothetical scenario is this:
I have an API call to a server which responds with a userID. I then use that userID to make a request to another database server which returns a set of data that carries out some machine learning processing that can take several minutes.
Due to the latency, the task is put onto a task queue and once it is complete it updates a NoSql database entry with from isComplete: false to isComplete: true. This means that we then need to poll the database every n seconds until we get a response indicating isComplete: true and then we cease the polling. I understand there are a number of solutions to polling an api but I have yet to see one involving promises, conditional polling, and not following some of the anti-patterns mentioned in the previously linked post. If I have missed anything and this is a repeat I do apologize in advance.
So far the process is outlined by the code below:
let state = false;
const userIdApi = () => {
return new Promise((res, rej) => {
console.log("userIdApi");
const userId = "uid123";
setTimeout(()=> res(userId), 2000)
})
}
const startProcessingTaskApi = userIdApi().then(result => {
return new Promise((res, rej) => {
console.log("startProcessingTaskApi");
const msg = "Task submitted";
setTimeout(()=> res(msg), 2000)
})
})
const pollDatabase = (userId) => {
return new Promise((res, rej) => {
console.log("Polling databse with " + userId)
setTimeout(()=> res(true), 2000)
})
}
Promise.all([userIdApi(), startProcessingTaskApi])
.then(([resultsuserIdApi, resultsStartProcessingTaskApi]) => {
const id = setTimeout(function poll(resultsuserIdApi){
console.log(resultsuserIdApi)
return pollDatabase(resultsuserIdApi)
.then(res=> {
state = res
if (state === true){
clearTimeout(id);
return;
}
setTimeout(poll, 2000, resultsuserIdApi);
})
},2000)
})
I have a question that relates to this code as it is failing to carry out the polling as I need:
I saw in the accepted answer of the post How do I access previous promise results in a .then() chain? that one should "Break the chain" to avoid huge chains of .then() statements. I followed the guidance and it seemed to do the trick (before adding the polling), however, when I console logged out every line it seems that userIdApi is executed twice; once where it is used in the startProcessingTaskApi definition and then in the Promise.all line.
Is this a known occurrence? It makes sense why it happens I am just wondering why this is fine to send two requests to execute the same promise, or if there is a way to perhaps prevent the first request from happening and restrict the function execution to the Promise.all statement?
I am fairly new to Javascript having come from Python so any pointers on where I may be missing some knowledge to be able to get this seemingly simple task working would be greatly appreciated.
I think you're almost there, it seems you're just struggling with the asynchronous nature of javascript. Using promises is definitely the way to go here and understanding how to chain them together is key to implementing your use case.
I would start by implementing a single method that wraps setTimeout to simplify things down.
function delay(millis) {
return new Promise((resolve) => setTimeout(resolve, millis));
}
Then you can re-implement the "API" methods using the delay function.
const userIdApi = () => {
return delay(2000).then(() => "uid123");
};
// Make userId an argument to this method (like pollDatabase) so we don't need to get it twice.
const startProcessingTaskApi = (userId) => {
return delay(2000).then(() => "Task submitted");
};
const pollDatabase = (userId) => {
return delay(2000).then(() => true);
};
You can continue polling the database by simply chaining another promise in the chain when your condition is not met.
function pollUntilComplete(userId) {
return pollDatabase(userId).then((result) => {
if (!result) {
// Result is not ready yet, chain another database polling promise.
return pollUntilComplete(userId);
}
});
}
Then you can put everything together to implement your use case.
userIdApi().then((userId) => {
// Add task processing to the promise chain.
return startProcessingTaskApi(userId).then(() => {
// Add database polling to the promise chain.
return pollUntilComplete(userId);
});
}).then(() => {
// Everything is done at this point.
console.log('done');
}).catch((err) => {
// An error occurred at some point in the promise chain.
console.error(err);
});
This becomes a lot easier if you're able to actually use the async and await keywords.
Using the same delay function as in Jake's answer:
async function doItAll(userID) {
await startTaskProcessingApi(userID);
while (true) {
if (await pollDatabase(userID)) break;
}
}

Async / await vs then which is the best for performance?

I have a simple code in JavaScript that execute a request in an API and return the response, simple. But in this case I will have thousands of requests. So, which one of the code options will perform better, and why. Also which one is recommended as good pratices these days?
First options is using the .then to resolve the promises and the seccond one is using async / await.
In my tests the two options had very similar results without significant differences, but I'm not sure in scale.
// Using then
doSomething(payload) {
const url = 'https://link-here/consultas';
return this.axios.get(url, {
params: {
token: payload.token,
chave: payload.chave,
},
}).then(resp => resp.data);
}
// Using Async / await
async doSomething(payload) {
const url = 'https://link-here/consultas';
const resp = await this.axios.get(url, {
params: {
token: payload.token,
chave: payload.chave,
},
});
return resp.data;
}
Any explanation will be of great value.
From a performance point of view, await is just an internal version of .then() (doing basically the same thing). The reason to choose one over the other doesn't really have to do with performance, but has to do with desired coding style or coding convenience. Certainly, the interpreter has a few more opportunities to optimize things internally with await, but its unlikely that should be how you decide which to use. If all else was equal, I would choose await for the reason cited above. But, I'd first choose which made the code simpler to write and understand and maintain and test.
Used properly, await can often save you a bunch of lines of code making your code simpler to read, test and maintain. That's why it was invented.
There's no meaningful difference between the two versions of your code. Both achieve the same result when the axios call is successful or has an error.
Where await could make more of a convenience difference is if you had multiple successive asynchronous calls that needed to be serialized. Then, rather than bracketing them each inside a .then() handler to chain them properly, you could just use await and have simpler looking code.
A common mistake with both await and .then() is to forget proper error handling. If your error handling desire in this function is to just return the rejected promise, then both of your versions do that identically. But, if you have multiple async calls in a row and you want to do anything more complex than just returning the first rejection, then the error handling techniques for await and .then()/.catch() are quite different and which seems simpler will depend upon the situation.
There should be some corrections in this thread. await and .then are going to give very different results, and should be used for different reasons.
await will WAIT for something, and then continue to the next line. It's also the simpler of the two because it behaves mechanically more like synchronous behavior. You do step #1, wait, and then continue.
console.log("Runs first.");
await SomeFunction();
console.log("Runs last.");
.then splits off from the original call and starts operating within its own scope, and will update at a time the original scope cannot predict. If we can put semantics aside for a moment, it's "more asynchronous," because it leaves the old scope and branches off into a new one.
console.log("Runs first.");
SomeFunction().then((value) => {console.log("Runs last (probably). Didn't use await on SomeFunction().")})
console.log("Runs second (probably).");
As more explanation to #user280209 answer let's consider the following function which returns promise and compare its execution with .then() and async await.
function call(timeout) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`This call took ${timeout} seconds`);
resolve(true);
}, timeout * 1000);
});
}
With .then()
(async () => {
call(5).then((r) => {
console.log(r);
});
await call(2); //This will print result first
await call(1);
})();
When running the above call the logs will be
This call took 2 seconds
This call took 1 seconds
This call took 5 seconds
true
As we can see .then() didn't pause the execution of its below line until it completes.
With async/wait
(async () => {
await call(5); //This will print result first
await call(2);
await call(1);
})();
When run the above function logs will be
This call took 5 seconds
This call took 2 seconds
This call took 1 seconds
So I think if your promise's result won't be used in the following lines, .then() may be better.
For those saying await blocks the code until the async call returns you are missing the point. "await" is syntactic sugar for a promise.then(). It is effectively wrapping the rest of your function in the then block of a promise it is creating for you. There is no real "blocking" or "waiting".
run();
async function run() {
console.log('running');
makePromises();
console.log('exiting right away!');
}
async function makePromises() {
console.log('make promise 1');
const myPromise = promiseMe(1)
.then(msg => {
console.log(`What i want to run after the promise is resolved ${msg}`)
})
console.log('make promise 2')
const msg = await promiseMe(2);
console.log(`What i want to run after the promise is resolved via await ${msg}`)
}
function promiseMe(num: number): Promise<string> {
return new Promise((resolve, reject) => {
console.log(`promise`)
resolve(`hello promise ${num}`);
})
}
The await line in makePromises does not block anything and the output is:
running
make promise 1
promise
make promise 2
promise
exiting right away!
What i want to run after the promise is resolved hello promise 1
What i want to run after the promise is resolved via await hello promise 2
Actually.
Await/Async can perform more efficiently as Promise.then() loses the scope in which it was called after execution, you are attaching a callback to the callback stack.
What it causes is: The system now has to store a reference to where the .then() was called. In case of error it has to correctly point to where the error happens, otherwise, without the scope (as the system resumed execution after called the Promise, waiting to comeback to the .then() later) it isn't able to point to where the error happened.
Async/Await you suspend the exection of the method where it is being called thus preserving reference.
If we just consider performance(time taken) then it actually depends on whether your operations are serial or parallel. If your tasks are serial then there will be no difference between await and .then. But if your tasks are parallel then .then will take less time. Consider the following example
let time = Date.now();
// first task takes 1.5 secs
async function firstTask () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
},1500)
})
}
// second task takes 2 secs
async function secondTask () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
},2000)
})
}
// using await
async function call(){
const d1 = await firstTask();
const d2 = await secondTask();
console.log(Date.now()-time, d1+d2)
}
call()
// using .then
async function call2(){
let d1=null,d2=null;
firstTask().then(data => {
d1=data;
if(d2){
console.log(Date.now()-time, d1+d2);
}
})
secondTask().then(data => {
d2=data;
if(d1){
console.log(Date.now()-time, d1+d2);
}
})
}
call2()
Here are the two tasks, first takes 1.5 secs and second takes 2 secs. Call function uses await where as call2 function uses .then . The output is as follows
From call2 2012 3
From call 3506 3
I hope it helps.
As far as I understand .then() and await are not the same thing. An async function won't proceed with the next command until the promise is resolved/rejected since it's basically an implementation of generators. On the contrast, in the case of .then(), the execution of the function will proceed with the next command and the resolve/reject callback will be executed "when there's time" aka when the current event loop (not entirely sure about that part) will be completed.
tldr; on a single promise await and .then() behave similarly but when one promise needs another one to be resolved first then the two of them behave entirely different
Many answer have been provided to this question already. However, to point out key information in the answers above and from my understanding, note below point:
only use await when not handling error return
if no crucial need for error handling use await instead
use .then .catch if returned error message or data is crucial for debugging / or proper error handling instead of try catch for await
Choose any prefer method from code sample below
const getData = (params = {name: 'john', email: 'ex#gmail.com'}) => {
return axios.post(url, params);
}
// anywhere you want to get the return data
// using await
const setData = async () => {
const data = await getData();
}
// to handle error with await
const setData = async () => {
try {
const data = await getData();
}
catch(err) {
console.log(err.message);
}
}
// using .then .catch
const setData = () => {
var data;
getData().then((res) => {
data = res.data; console.log(data)
}).catch((err) => {
console.log(err.message);
});
}

How to handle for promise inside a piped map

I am definitely sure I am confused here so please any help is appreciated.
Here is my scenario:
I pull from Firestore a document:
return this.afs.collection("events").doc(eventID).snapshotChanges().pipe(
map( document => {
})
);
All is fine up to here.
But inside the map I need a promise to resolve (or not)
For example:
return this.afs.collection("events").doc(eventID).snapshotChanges().pipe(
map( document => {
// This is a promise the below part
const data = await EventImporterJSON.getFromJSON(document.payload.data())
return data
})
);
I understand that the await cannot happen there. I am very confused how to solve this, perhaps I have not worked long enough with observables and rxjs.
In the end what I am trying to achieve is:
Get the document. Map and process it but inside the process, I need to handle a promise.
I don't want to return that promise to the caller though.
Does this make sense?
Or have I structured this completely wrong?
This is a typical use-case for mergeMap or concatMap:
return this.afs.collection("events").doc(eventID).snapshotChanges().pipe(
mergeMap(document => {
// This is a promise the below part
return EventImporterJSON.getFromJSON(document.payload.data())
})
);
However, you can also use async - await because operators such as mergeMap handle Observables, Promises, arrays, etc. the same way, so you can just return a Promise in mergeMaps project function it will work fine.
Typically, you don't need to use multiple awaits in a single method because the more "Rx" way of doing things is chaining operators, but if you want, you can because the async method returns a Promise and RxJS will handle it like any other Promise.
const delayedPromise = () => new Promise(resolve => {
setTimeout(() => resolve(), 1000);
})
of('a').pipe(
mergeMap(async v => {
console.log(1);
await delayedPromise();
console.log(2);
await delayedPromise();
console.log(3);
await delayedPromise();
return v;
})
).subscribe(console.log);
// 1
// 2
// 3
// a
Live demo: https://stackblitz.com/edit/rxjs-3fujcs
Observables can be seen as a layer up to promises, why don't you use your promise this way ?
like this :
let getDataFromJson(payloadData){
return from(EventImporterJSON.getFromJSON(payloadData());
}
return this.afs.collection("events").doc(eventID).snapshotChanges().pipe(
map(document=>document.payload.data),
switchMap( payloadData=> getDataFromJson(payloadData)))
.subscribe(result=>{
//final result
});
1 pipe your first observable with map just to simplify your returner value
2 switchMap to another observable which will be your promise as an Observable ( with the "from" operator);
The map operator is made for improve result in synchronous and "pure" way like return only few properties of an object or filter a data, here you want to chain two async operation so I suggest you to keep it in a rx approach

Timing when converting promise to async await

I have the following block
return this
.configure(config => {
viewModel.configure(config, this);
return config;
})
.then(() => {
this.activate();
});
which, is equivalent to following block, suggested by vscode auto promise->async/await conversion:
await this.configure(config => {
viewModel.configure(config, this);
return config;
});
this.activate();
My question is are they actually the same in timing? In the promise example, shouldn't the second fn in then() 1 micro task away from the callback that accepts config?
Extra question for clarity: are the following equal in timing perspective:
Promise.all(tasks)
.then(() => {
othertasks.forEach(() => doStuff());
})
.then(() => {
prune();
});
and:
await Promise.all(tasks);
othertasks.forEach(() => doStuff();
await Promise.resolve();
prune();
EDIT: I should clarify, with regards to Andrea Giammarchi's answer, that my answer was purely and only related to the difference in number of ticks in-between the synchronously executed code, not whether the result of that code as-written is actually equivalent (obviously, the non-awaited promises would yield promises whereas the awaited promises would yield the values)
This would make more sense in the context that me and bigopon had a discussion in a github issue where he accepted VS Code's suggestion to remove a "redundant" .then from a promise chain in a piece of legacy code that happens to be sensitive to subtle timing issues.
I pointed out that this change would cause a particular method to be executed one tick earlier, and that the effects of that could potentially break complex apps relying on these (quirky) timings.
The discussion was then, whether this:
somePromise.then(() => {
...
}).then(() => {
doStuff();
})
Would have the same timings as this:
await somePromise;
doStuff();
To which my answer was: no, the doStuff() in the second snippet would execute one tick earlier.
When someone else suggested that await or .then would actually be executed synchronously if the passed-in promise was already resolved, that motivated me to write this answer and clarify why not.
I do realize that without this context, my answer can seem misleading, but again: it's just to point out the similarity in number of ticks.
Original answer
Example 1
For resolving a value, in plain terms, this:
await something
Is equivalent to this:
Promise.resolve(something).then()
They both result in a pending promise.
Example 2
For queueing a task, this:
await Promise.resolve();
doStuff();
Is equivalent to this:
Promise.resolve().then(() => {
doStuff();
})
In both cases, doStuff() happens on the next tick.
In order to determine whether a regular .then chain is equivalent to a series of awaits, you simply need to count .then and await. If the number of each is the same between two given pieces of code, then the amount of time/ticks/whatever passing between those pieces of code will be the same.
Example 3
Another example, this:
await Promise.resolve();
doStuff();
await Promise.resolve();
doStuff();
await Promise.resolve();
await Promise.resolve();
doStuff();
Is equivalent to this:
Promise.resolve()
.then(() => {
doStuff();
})
.then(() => {
doStuff();
})
.then(() => {})
.then(() => {
doStuff();
})
Note that the Promise.resolve() itself has no effect on the timings. It returns a resolved promise. It's the then() / await that turns it into a pending one.
So I respectfully disagree with amadan and I believe both your examples are equivalent.
What the spec says
If promise.[[PromiseState]] is "pending", then
a. Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]].
b. Append rejectReaction as the last element of the List that is promise.[[PromiseRejectReactions]].
Else if promise.[[PromiseState]] is "fulfilled", then
a. Let value be promise.[[PromiseResult]].
b. Perform EnqueueJob("PromiseJobs", PromiseReactionJob, « fulfillReaction, value »).
What this says is "if the promise is already pending, simply append the fulfillReaction of that pending promise, but of the promise is fulfilled, then enqueue a new job".
In other words, .then is guaranteed to return a pending promise regardless of whether the promise it is chained on was fulfilled or not.
I think there is quite some confusion in both what happened with VSCode, what you asked as question, and the kind of answer you received.
I'll try to clarify all points as I can, hoping I got the question right.
Let me start saying that ...
Those two blocks are not equivalent
The following piece of code:
this
.configure(config => {
viewModel.configure(config, this);
return config;
})
.then(value => `some ${value}`);
is "equivalent" only to this one:
await this
.configure(config => {
viewModel.configure(config, this);
return config;
})
.then(value => `some ${value}`);
That is because await has less priority than method chaining/then concatenation.
(async function (){
const then = await Promise.resolve(1).then(one => one + one);
console.log(then); // this is number 2
const something = await 123;
console.log(something); // this is number 123
}());
The reason you are rightly confused is that VSCode outsmarted your intent.
return this
.configure(config => {
viewModel.configure(config, this);
// configure() returns a Promise
// and config can be a Promise too
return config;
})
.then(() => {
// but you are not using here the config value
// or, if it was a promise, whatever value it resolved
// and you are also not returning any value
this.activate();
});
Since VSCode knows that configure is thenable, and its returned value could also be a Promise, which would imply the activate can happen only after config is eventually resolved, it also knows having an extra tick would make no sense because you don't need whatever config returned, either as value or promise, so that activate can be called right away.
Since you were also not returning any value in the last then, the whole return can be dropped.
// only async to wait for
await this.configure(config => {
viewModel.configure(config, this);
return config;
});
// with config either value or promise
// there's nothing else to wait for, so
// let's invoke activate without returning
// anything, producing is the same undefined result
this.activate();
To recap what happens inside that await:
(async function (){
const config = new Promise(res => setTimeout(res, 1000));
console.time('awaiting');
const value = await Promise.resolve(1).then(() => {
return config;
});
console.timeEnd('awaiting');
// awaiting: 1000.XXXms
}());
If you were by any chance using the returned value inside that last then, you would've seen that VSCode could not have dropped it, most-likely readdressed as const value = await ...; this.activate(value); which is also still OK.
To the previous comment stating:
For resolving a value, in plain terms, this:
await something
Is equivalent to this:
Promise.resolve(something).then()
They both result in a pending promise.
Not sure I read that wrong but that that felt to me quite a misleading statement.
const resolved = await anything means resolved is always a value, never a pending promise.
That's quite possibly the whole point of await: it won't stop awaiting until there is a value.
Example:
(async function (){
const something = Promise.resolve(Math.random());
// this logs the random number as typeof number
console.log(await something);
// this also logs the random number as typeof number
console.log(await Promise.resolve(something).then());
// while this is one is the only pending promise
console.log(Promise.resolve(something).then());
}());
The reason you eventually see Pending promise in console is that the AIIFE (Asynchronous Immediately Invoked Function Expression) is a promise itself and you can await it elsewhere.
You can see that returning a value or a pending promise will always produce the expected result.
(async function (){
// instant return, it's just fine
// return 123;
// return promise (unnecessary ticks added)
return Promise.resolve(123).then();
}()).then(console.log);
In both cases the 123 number is logged.
I hope it's clear now, specially for the OP, what happened in VSCode, and, specially, why happened.
Regards.

What is a nice way to override the value a promise resolves to? [duplicate]

This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 5 years ago.
I am writing a Node.js script to populate an SQL database with a test dataset.
In the promise chain illustrated in the code snippet below (the real code is a bit more hairy), the function insertData() requires the db object to be passed through from the previous stage. However, asynchronous calls inside dropAndCreateTables() on the previous stage use db object but do not return it. I came up with a solution that wraps promises inside dropAndCreateTables() into another promise object that resolves to a db object. However:
I heard that using Promise() constructor in non-library code is an antipattern and may lead to subtle and hard-to-diagnose mistakes
I heard that nesting then()-chains is also an antipattern
It does not allow me to ignore errors from the promiseDrop (for example, I don't care if tables don't exist on drop)
It is ugly
Questions:
Is there a simpler, nicer and more socially accepted way to override the return value of a promise? (in this case, created with Promise.all())
Is there a way to restructure my code in a way that this problem does not occur? (That is, I don't exclude the possibility of "XY problem" here)
Code:
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const sqlDrop = fs.readFileSync('drop.sql').toString()
const promiseDrop = db.raw(sqlDrop)
const sqlCreate = fs.readFileSync('create.sql').toString()
const promiseCreate = db.raw(sqlCreate)
/********* Problems here? ************************************/
return new Promise((resolve, reject) => { // Ew?
Promise.all([promiseDrop, promiseCreate])
.then(() => {
resolve(db) // Override the returned value
})
.catch(reject)
})
}
return Promise.resolve(db)
}
initDB({ debug: false })
.then((db) => {
return dropAndCreateTables(db, START_CLEAN) // Without my hack this does not return `db`
})
.then((db) => {
return insertData(db, DO_UPSERT) // This needs the `db` object
})
.then(() => {
console.info(`\n${timestamp()} done`)
})
.catch(handleError)
(Some fairly important notes midway and later in the answer, please do read all the way to the end.)
Is there a simpler, nicer and more socially accepted way to override the return value of a promise? (in this case, created with Promise.all())
Yes, you simply return a value from the then handler, and return the promise then returns:
return Promise.all([promiseDrop, promiseCreate])
.then(() => db);
then (and catch) create promise chains. Each link in the chain can transform the result. then and catch return a new promise that will be fulfilled or rejected based on what happens in their callback:
If their callback throws, the promise rejects with the error thrown
If their callback returns a non-thenable value (e.g., promise), the promise is fulfilled with that value
If their callback returns a thenable value, the promise is resolved to that thenable — it waits for the other promise to settle, then settles the same way
(If the term "thenable" isn't familiar, or you're not clear on the distinction between "fulfill" and "resolve," I go into promise terminology in this post on my blog.)
I heard that using Promise() constructor in non-library code is an antipattern and may lead to subtle and hard-to-diagnose mistakes
The distinction isn't library code vs. non-library code, it's between code that doesn't already have a promise to work with and code that does. If you already have a promise to work with, you almost never want to use new Promise. More: What is the explicit promise construction antipattern and how do I avoid it?
I heard that nesting then()-chains is also an antipattern
You almost never need to nest then chains, because again each link in the chain already has the means of tranforming the result passing through it. So:
// Unnecessary nesting
doSomething()
.then(a => {
return doSomethingElse(a * 2)
.then(b => b * 3);
})
.catch(e => { /*...handle error...*/ });
can be more idiomatically and simply written:
doSomething()
.then(a => doSomethingElse(a * 2))
.then(b => b * 3);
.catch(e => { /*...handle error...*/ });
Is there a way to restructure my code in a way that this problem does not occur? (That is, I don't exclude the possibility of "XY problem" here)
Not an X/Y per se, but you have a problem in that code: There's no guarantee the drop will happen before the create! So instead of starting both and letting them run in parallel and watching for the results with Promise.all, ensure those operations happen in sequence:
// Fairly minimal changes
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const sqlDrop = fs.readFileSync('drop.sql').toString()
return db.raw(sqlDrop)
.then(() => {
const sqlCreate = fs.readFileSync('create.sql').toString()
return db.raw(sqlCreate);
})
.then(() => db);
}
return Promise.resolve(db)
}
But, I wouldn't use sync file I/O. Instead
const promisify = require("utils").promisify;
const readWithPromise = promisify(fs.readFile);
and then
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const getDrop = readWithPromise('drop.sql'); // Start this first
const getCreate = readWithPromise('create.sql'); // Then start this
return getDrop
.then(dropSql => db.raw(dropSql)) // Got the drop SQL, run it
.then(() => getCreate) // Make sure we have the create SQl
.then(createSql => db.raw(createSql)) // Run it
.then(() => db);
}
return Promise.resolve(db)
}
Note how we avoid ever busy-waiting on I/O, and we can overlap the DB's drop operation with reading the create SQL.
You don't need to call the Promise constructor when returning another promise, you can just write it like:
return Promise.all([promiseDrop, promiseCreate])
.then(() => db)
.catch(error => {
// handle the error or rethrow it
})
You might omit resolving db from dropAndCreateTables like this:
.then((db) => {
return dropAndCreateTables(db, START_CLEAN).then(Promise.resolve(db));
})
You should not let dropAndCreateTables return a db promise, there is no real usecase for it. So:
return Promise.all([promiseDrop, promiseCreate]);
is enough. Now the chaining part:
initDB({ debug: false }).then(async (db) => {
await dropAndCreateTables(db, START_CLEAN);
await insertData(db, DO_UPSERT);
console.info(`\n${timestamp()} done`)
}).catch(handleError)

Categories