Promise not waiting on one another - javascript

So my understanding of promises lead me to believe that my other promises would run one after another in my then chain but I'm doing something wrong here.
The code I'm using currently is as follows
const mainPromise = () => Promise.resolve(
console.log('spinner spinning...')
...spinner code.... //this is omitted code
);
const getData = () => Promise.resolve(
someObj.getProducts('data.json')
);
const updateProduct = () => Promise.resolve(
setTimeout(()=>{
someObj.updateProductHTML()
}, 0)
);
const updateDom = () => {
setTimeout(()=>{
someObj.updateDOM()
}, 0)
};
and my promise chain
mainPromise()
.then(getData)
.then(updateProduct)
.then(updateDom)
;
They seem to be initially running in order but the Ajax call I have in getProducts also has a for loop worker to build my array of objects and is finishing after all my .thens run.
I am attempting to at least have my data call and worker finish before updateProduct and updateDOM runs
--- UPDATE ---
ok so with the revised promises set up as such as per suggestions in the comments and Samanime's answer
const mainPromise = () => Promise.resolve(
console.log('spinner spinning...')
);
const getData = () => new Promise( resolve => {
console.log('getData');
someObj.getProducts('data.json');
resolve();
}
);
const updateProduct = () => new Promise( resolve =>{
console.log('updateProduct');
someObj.updateProductHTML();
resolve();
});
//execute promise chain
mainPromise()
.then(getData)
.then(updateProduct)
.then(page.updateDOM)
;
I updated the promises to not immediately resolve and am attempting to call resolve after I call my functions ( although I'm uneasy as to if resolve will be called before or after these functions ).
Unfortunately, I'm still getting the same behavior. I've added console logs to my functions as well as my promises and I'm getting this list back
log.spinner spinning
log.getData
log.updateProduct
log.A log from the function updateProduct calls
log.48 product objects created (for loop worker in my getProducts function)
log.Data retrieved and assigned
the last two logs would ideally be called after getData
None of the calls or functions outside of the ones provided are return promises, I'm working on legacy code and I'm moving away from the setTimeout trick as well as my results weren't consistent.
--UPDATE 2 --
The problem I'm having is known as Forking/Splitting. I just need to figure out chaining specifically to fix my issue.
-- FINAL --
this is what I ended up working out
// execute promise chain
mainPromise()
.then(getData);
//the timeout is a little hack to ensure the sequence is kept
mainPromise()
.then(() => {
setTimeout(() => {
myObj.updateProductHTML();
myObj.updateDOM();
}, 0);
});
apparently .then(foo).then(bar) just runs foo and bar at the same time
seems to be working ok right but I feel like somethings not right with it still.

I believe it's because Promise.resolve() doesn't do quite what you think it does.
Promise.resolve() creates a new Promise and resolves it immediately using the value of what it's given. Things like setTimeout return their id (an integer) immediately, so they aren't doing what you want. Your getProducts() is probably an async call, so it may be returning null or something as well (if it's returning a Promise or returns the value synchronously, then it's fine).
You're better off writing a normal Promise and calling resolve() at the appropriate time.
const mainPromise = () => Promise.resolve(
console.log('spinner spinning...')
...spinner code....
);
// Assuming it's already returning a Promise or synchronous response. If it isn't, then deal with it like the setTimeout ones below.
const getData = () => someObj.getProducts('data.json')
const updateProduct = () => new Promise(resolve => {
setTimeout(()=>{
someObj.updateProductHTML();
resolve();
}, 0)
});
// You don't NEED to in your example since it's at the end of the chain, but you probably want to wrap this too in case you add to the chain.
const updateDom = () => new Promise(resolve => {
setTimeout(()=>{
someObj.updateDOM();
resolve();
}, 0)
});

Related

Return in .then(), when there are multiple .then()?

Js is not my first language, by a long chalk. I code in 5 or 6 others, and am comfortable with callbacks, which means that I should be able to grok promises.
However, I inherited a node.js project with something like this (simplified), which makes me uncertain)
let promise = someFunction.then({return xxxx;});
// lots of more code & promises
Promises.all().then(() => {return somethingElse});
I am comfortable with lots of promises and Promises.all() to wait for them all to resolve (although I am replacing all .then with await for cleaner code), BUT ...
that return inside a .then disturbs me. If I understand correctly, .then just wraps an async callback - which could happen at any time, even before the rest of the code and that Promises.all, leaving some of the code unexecuted.
Am I correct to be concerned, or is there something that I am missing?
Tl;dr - is it ok to return inside a .then?
Returning inside .then is extremely common and fine to do. What it does is it produces a Promise that now resolves to the value returned (rather than to the value returned by the previous Promise). Example:
// resolves to 1
const prom1 = Promise.resolve(1);
// chains off of prom1, resolves to 2
const prom2 = prom1.then(() => {
return 2;
});
prom1.then(console.log);
setTimeout(() => {
prom2.then(console.log);
});
This technique is very useful when you need to pass along a value from a prior .then to a subsequent .then.
const makeApiCall = num => Promise.resolve(num + 3);
Promise.resolve(1)
.then((result1) => {
return makeApiCall(result1);
})
.then((result2) => {
console.log(result2);
});
What the return will do in your situation specifically:
let promise = someFunction.then({return xxxx;});
// lots of more code & promises
Promises.all().then(() => {return somethingElse});
If promise is passed to the Promise.all, then its resolve value will be xxxx, rather than whatever someFunction resolves to. (If the Promise.all's .then ignores its argument, then it does nothing except wait for the returned value to resolve, if it's a Promise.)
const someFunction = () => Promise.resolve(1);
let promise = someFunction().then(() => {
return 'xxxx';
});
Promise.all([promise]).then((allResults) => {
console.log('allResults', allResults);
return 'somethingElse';
})
.then((result2) => {
console.log('Next `.then`', result2);
});
Note that your current code has a number of syntax issues you need to correct.

Break out of for(ever) loop by resolved Promise

I hope this is a simple question but I currently can't wrap my head around.
What I want to do is break out of a while loop which contains a delay when a promise gets resolved.
In pseudocode this would look like:
while( ! promise.resolved ){
doSomthing()
await sleep( 5min )
}
The loop must break instantly after the promise is resolved and not wait for sleep to finish.
sleep is currently implemented trivially by setTimeout but can be implemented differently.
I would like to have some kind of spatial separation between the awaited promise and sleep to show its working more clearly*) (and because I hope for an elegant solution to learn from).
So what would work but I don't like is something like:
while( true ){
doSomething()
try {
await Promise.race([promise,rejectAfter(5000)])
break
} catch( e ){}
}
If you must know:
doSomething is sending out status information.
promise is waiting for user interaction.
*) Part of the purpose of this code is to show/demonstrate others how things are expect to work. So I'm looking for the clearest solution on this level of implementation.
One approach would be to wait for the promise result, assuming it is a truthy value1:
const promise = someTask();
let result = undefined;
while (!result) {
doSomething();
result = await Promise.race([
promise, // resolves to an object
sleep(5000), // resolves to undefined
]);
}
1: and if it isn't, either chain .then(_ => true) to the promise, or make sleep fulfill with a special value that you can distinguish from everything someTask might return (like the symbol in Jeff's answer).
Also works nicely with a do-while loop, given result is always undefined at first:
const promise = someTask();
let result = undefined;
do {
doSomething();
result = await Promise.race([
promise, // resolves to an object
sleep(5000), // resolves to undefined
]);
} while (!result);
A downside here is that if doSomething() throws an exception, the promise is never awaited and might cause an unhandled rejection crash when the task errors.
Another approach would be not to use a loop, but an old-school interval:
const i = setInterval(doSomething, 5000);
let result;
try {
result = await someTask();
} finally {
clearInterval(i);
}
A downside here is that doSomething is not called immediately but only after 5s for the first time. Also, if doSomething throws an exception, it will instantly crash the application. It still might be a good approach if you don't expect doSomething to throw (or handle each exception in the setInterval callback and expect the "loop" to carry on).
The "proper" approach that will forward all exceptions from both someTask() and doSomething() could look like this:
let done = false;
const result = await Promise.race([
(async() => {
while (!done) {
doSomething();
await sleep(5000)
}
})(),
someTask().finally(() => {
done = true;
}),
]);
(Instead of .finally(), you can also wrap the Promise.race in a try-finally, like in approach two.)
The only little disadvantage in comparison to approach two is that sleep(5000) will keep running and is not immediately cancelled when someTask finishes (even though result is immediately available), which might prevent your program from exiting as soon as you want.
Minor modification of your idea to make it work better:
const sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms));
async function waitAndDo(promise) {
let resolved = false;
const waitFor = promise
.then((result) => resolved = true);
while(!resolved) {
doSomething();
await Promise.race([waitFor, sleep(5000)]);
}
}
The function accepts a promise and will be working until it resolves.
The waitFor promise will finish after promise is fulfilled and resolved updated to true.
The while loop can then loop until the resolved variable is set to true. In that case, the loop will end an execution continues after it.
Inside the loop, Promise.race() will ensure that it will stop awaiting as soon as the promise resolves or the sleep expires. Whichever comes first.
Therefore, as soon as the promise gets resolve, the .then() handler triggers first and updates resolved. The await Promise.race(); will end the waiting after and the while loop will not execute again, since resolved is now true.
Seems like you would just use some sort of interval and kill it when the promise is done.
const updateMessage = (fnc, ms, runInit) => {
if (runInit) fnc();
const timer = window.setInterval(fnc, ms);
return function () {
console.log('killed');
timer && window.clearTimeout(timer);
}
}
const updateTime = () => {
document.getElementById("out").textContent = Date.now();
}
const updateEnd = updateMessage(updateTime, 100, true);
new Promise((resolve) => {
window.setTimeout(resolve, Math.floor(Math.random()*5000));
}).then(updateEnd);
<div id="out"></div>
As an alternative to VLAZ's very reasonable answer, you can avoid the separate boolean sentinel by having your sleep function return some kind of unique sentinel return value that indicates the the timeout. Symbol is exactly the kind of lightweight, unique object for this use case.
function sleepPromise(ms, resolveWith) {
return new Promise(resolve => {
setTimeout(resolve, ms, resolveWith);
});
}
const inputPromise = new Promise(
resolve => document.getElementById("wakeUp").addEventListener("click", resolve));
async function yourFunction() {
const keepSleeping = Symbol("keep sleeping");
do {
/* loop starts here */
console.log("Sleeping...");
/* loop ends here */
} while (await Promise.race([inputPromise, sleepPromise(3000, keepSleeping)]) === keepSleeping);
console.log("Awake!");
}
yourFunction();
<button id="wakeUp">Wake up</button>

Promise.resolve vs Promise.resolve().then()

A question asked here before, with the exact same title as this one, was answered with a "You should not use that, use this instead", I am looking to know what it does, not what else could I do, it's about understanding not a simple copy a paste.
My question is quite simple, what is the difference between these three approaches when creating a promise?
const API = (item, fail) =>
new Promise((resolve, reject) => {
if (fail) reject(item + ' ...with an error');
setTimeout(() => resolve(item), 1000);
});
(async () => {
const pro1 = Promise.resolve(API('I am inside resolve'));
const pro2 = Promise.resolve(API('I am inside resolve', true));
const pro3 = Promise.resolve().then(() => API('I am thenable'));
const pro4 = Promise.resolve().then(() => API('I am thenable', true));
const pro5 = new Promise((resolve) => resolve(API('I am a new promise')));
const pro6 = new Promise((resolve) => resolve(API('I am a new promise', true)));
const store = [pro1, pro2, pro3, pro4, pro5, pro6];
const results = await Promise.allSettled(store);
for (const { status, value, reason } of results) {
if (status === 'fulfilled') console.log(value)
else console.log(reason)
}
})();
The difference is in job to be done. While all of this methods are valid, they have different cost and predictability.
Promise.resolve() produces single resolved Promise instance, and depending on value provided to the call JS engine have information to optimize it. It makes all work to be done in a single call to underlying code of the JS engine (usually C++, but could be Java or WASM). So it's always the best choice.
Promise.resolve().then(() => API(/*...*/)) Produce several Promise instances: one at Promise.resolve() and other at .then() call. It also allocates more memory and make several (3 or more) redundant jumps between JS and the engine. It's hardly optimizable and requires intensive heuristics to be performed to figure out is this call optimizable. It's the worst option.
new Promise((resolve) => resolve(API(/* ... */)) allocates one function and one Promise instance and makes two jumps between JS and the engine. It's harder to optimize this call, due to nature of JS.
Promise.resolve().then()
In your examples, the then() makes no difference as you just resolve the promise, and get its data.
then() is typically used to chain promises, take this as an example:
Promise.resolve('foo')
// 1. Receive "foo", concatenate "bar" to it, and resolve that to the next then
.then(function(string) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
string += 'bar';
resolve(string);
}, 1);
});
})
// 2. receive "foobar", register a callback function to work on that string
// and print it to the console, but not before returning the unworked on
// string to the next then
.then(function(string) {
setTimeout(function() {
string += 'baz';
console.log(string); // foobarbaz
}, 1)
return string;
})
Here we chain multiple promises then() off of a previously resolved promise, while maintaining the original data from the first resolve, though in this case we modify it with every new promise.
You can read more about promises and chaining here.

Remove actually removes a div after Promise is resolved

I'm removing content (a div) but first waiting for an animation (if none is provided, then the animation is just a resolved Promise by default). Check this out:
clearContent = (animation = Promise.resolve()) => {
return new Promise((resolve, reject) => {
const child = $('#child');
animation.then(animationEvent => {
const eventPackage = {
'divRemoved': 'divIDPlaceholder',
'itemRemoved': 'contentIDPlaceholder',
'remainingItemsCount': 2,
'remainingItems': 1
};
child.remove();
const contentRemovedEvent = new CustomEvent('contentRemovedFromPlaceholder', {
'detail': eventPackage
});
window.dispatchEvent(contentRemovedEvent);
console.log('Removed!');
return resolve(eventPackage);
});
});
}
const testAnimationThatTakesASecond = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve()
}, 1000);
});
}
$('#child').on('click', () => {
clearContent(testAnimationThatTakesASecond());
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="child">Remove me! The removal process can be animated as well!</div>
My constraints are that I'm waiting on an animation by anime.js and I'd like the whole removal process (that is the animation + the removal of the div itself) to be dependable on, because I might choose to chain some things based on it..
The problem is that although this is a wrapper function, I sometimes find that it doesn't work properly on low CPU. I'm thinking maybe because the .remove itself removes after the promise itself is resolved.
Am I wrong to think that a Promise will always wait for whatever code it has inside to finish?
Your function is fine, but from your last sentence and comments it seems like your expectation about promises is not quite correct.
If you are calling the code like this:
clearContent(testAnimationThatTakesASecond());
console.log('something after resolved');
The console.log will be executed before the animation finishes. The execution will not be halted in the calling context.
If you need to do something after the animation is resolved you need to use it like this:
clearContent(testAnimationThatTakesASecond())
.then(() => console.log('something after resolved'));
Or using async:
$('#child').on('click', async () => {
await clearContent(testAnimationThatTakesASecond());
console.log('something after resolved');
});
When you put async in front of a function you're telling that it will return a promise with the value from the return statement resolved!
When you await you're waiting for the promise to be fulfilled.
$('#child').on('click',async () => {
await testAnimationThatTakesASecond();
clearContent();
});
Edit:With promises also, we can use then to make sure that promise is resolved, and just then run the second phase.
Depend on our desired asynchronous order for our callbacks to get execute, if our they where doSomething() and doSomethingElse() to get them execute in order we can use
doSomething().then(doSomethingElse)
This is the only way that we are able to use the result of doSomething, inside doSomethingElse, It's like we ran:
doSomethingElse(resultOfDoSomething):
And also It's then-able in the way that if we had third operation, it get called after doSomethingElse finishes like: finalHandler(resultOfDoSomethingElse)

Why can I await this code but not use .then?

Node.JS 10.15, serverless, lambdas, invoked locally
SAMPLE A) This Works:
export async function main(event) {
const marketName = marketIdToNameMap[event.marketId];
const marketObject = marketDirectory[marketName];
const marketClient = await marketObject.fetchClient();
const marketTime = await marketObject.getTime(marketClient);
console.log(marketTime);
}
SAMPLE B) and this works:
export function main(event) {
const marketName = marketIdToNameMap[event.marketId];
const marketObject = marketDirectory[marketName];
marketObject.fetchClient().then((marketClient)=>{
marketObject.getTime(marketClient).then((result) => {
console.log('<---------------> marker 1 <--------------->');
console.log(result);
});
});
}
SAMPLE C) but this does not:
export async function main(event) {
const marketName = marketIdToNameMap[event.marketId];
const marketObject = marketDirectory[marketName];
const marketClient = await marketObject.fetchClient();
console.log('<---------------> marker 1 <--------------->');
marketObject.getTime(marketClient).then((result) => {
console.log('<---------------> marker 22 <--------------->');
console.log(result);
});
}
the guts of getTime are for all examples:
function getTime(marketClient){
return new Promise((resolve, reject) => {
return marketClient.getTime((err, result) => {
if (err) {
reject(err);
}
resolve(result);
});
}).catch(err => {
throw err;
});
}
clearly, it seems to be an issue with mixing async/awaits with classic promise then-ables. I would expect SAMPLE C to work because getTime() is returning a promise. However, the code simply finishes silently, never hitting the second marker. I have to put the first marker there just to be sure any code is run at all. It feels like i should be able to mix async/await and thenables, but i must not be considering something here.
#adrian, nope
You're neither awaiting nor returning the promise from marketObject.getTime().then(), and this will result in that promise chain executing independently, the main function returning and the process closing. Remember.. then returns a promise too.
the solution is
await marketObject.getTime(marketClient).then(...
or
return marketObject.getTime(marketClient).then(...
either way chains the promise to the main function such that whatever executes it consistently waits for all promises to resolve (or reject).
I suspect Sample B works because the main is not async and Lambda will wait for the event-loop to complete. i.e. it will execute the promise chain even though main returned early.
https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html
If you don't use callback in your code, AWS Lambda will call it
implicitly and the return value is null. When the callback is called,
AWS Lambda continues the Lambda function invocation until the event
loop is empty.
... and I suspect if you return a Promise (as you do in Sample C) then Lambda will simply terminate the process immediately once it resolves, which is does because you don't await/return the .then() chain, and thus the floating promise chain you've created will not execute.

Categories

Resources