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)
Related
From MDN:
Promise.all is rejected if any of the elements are rejected. For example, if you pass in four promises that resolve after a timeout and one promise that rejects immediately, then Promise.all will reject immediately.
Let's discuss the following code snippet:
(async () => {
try {
const awaited = await Promise.all([
(() => {
console.log('1');
return Promise.reject('2');
})(),
(() => {
console.log('3');
return Promise.resolve('4');
})(),
]);
console.log(awaited);
} catch (error) {
console.log(error);
} finally {
console.log('5');
}
})();
All the promises in the code above are immediately resolved/rejected. I think that the code should execute this way:
First console.log is executed, thus logging '1';
Promise is rejected with the value '2' thus fast-failing Promise.all();
'Catch' clause is executed, logging the rejected value of '2';
'Finally' clause is executed regardless, logging '5';
Instead, I see a '3' also being logged. Why is it so?
Instead, I see a '3' also being logged. Why is it so?
All the other answers try to explain how promises or the event loop work, but in fact it has nothing to do with those.
The argument you pass to Promise.all is an array that contains the results of two function calls. Therefore both functions will be called before Promise.all is called.
Both functions console.log a value and then return a promise.
Promise.all receives an array containing those promises. So before Promise.all does anything, both functions have already been executed and logged 1 and 3 respectively.
Maybe it's easier to see you restructure your code to create the promises beforehand:
(async () => {
try {
const promise1 = (() => {
console.log('1');
return Promise.reject('2');
})();
const promise2 = (() => {
console.log('3');
return Promise.resolve('4');
})()
const awaited = await Promise.all([promise1, promise2]);
console.log(awaited);
} catch (error) {
console.log(error);
} finally {
console.log('5');
}
})();
Just like with any other function, all function arguments are evaluated before the function itself is called. The order of events is rather this:
First console.log is executed, thus logging '1';
Rejected promise with 2 is created.
Second console.log is executed, thus logging '3';
Fulfilled promise with 4 is created.
Array containing both promises is created.
Promise.all is called with array of promises as parameter.
Promise.all processes promises.
'Catch' clause is executed, logging the rejected value of '2';
'Finally' clause is executed regardless, logging '5';
Here is a simpler example: Note that nothing is "done" with the second argument passed to the function and yet the value is still logged, precisely because all arguments are evaluated first:
function process(arg) {
return 'Received ' + arg;
}
const result = process(
(() => {
console.log('1');
return 1;
})(),
(() => {
console.log('3');
return 2;
})(),
);
console.log(result)
This is due to the way that Promises are handled in JavaScript. It is asynchronous in nature and there is no security as to the execution order. Please note that this is not an error but rather a feature of the language.
I would suggest you read more on the issue in this SO question and about the event loop.
The Promise.all() does fast-fail. However, it doesn't abort the other Promises from finishes. This example with timeouts illustrates it a little better.
Promise.all([
Promise.reject(),
new Promise((resolve, reject) => setTimeout(() => { console.log('a'); resolve(); }, 500)),
new Promise((resolve, reject) => setTimeout(() => { console.log('b'); resolve(); }, 2000))
])
.then(() => console.log('done'))
.catch(() => console.log('fail'));
You can see that the Promise.all() calls the catch() almost immediately, and stops waiting for the results of the other, because it won't change the outcome. However, the other two Promises aren't aborted in any way.
If you want to abort them, you'd have to implement your own logic to do that.
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>
I'm trying to implement promises in my code, but I'm having difficulty in grasping some aspects of it - namely the way one promise triggers another one in a chain. Take this, for instance:
HTML:
<div class="test"></div>
Javascript:
const testTag = document.querySelector('.test')
const firstPromise = new Promise((resolve) => {
testTag.addEventListener('click', () => {
doSomething()
})
resolve()
})
const secondPromise = () => {
firstPromise.then(() => {
setTimeout(function () {
console.log('This log happened 5 seconds after testTag was clicked')
}, 5000)
})
}
secondPromise()
But what happens is: the log happens instantly, it never waits for the click event to run. Why is that? Shouldn't a second promise only happen after the first one gets resolved?
the log happens instantly, it never waits for the click event to run.
You call resolve() immediately after creating the click event handler.
You don't wait for the click before calling resolve().
const firstPromise = new Promise((resolve) => {
testTag.addEventListener('click', () => {
doSomething()
resolve()
})
})
Note that unless doSomething() involves removing the event handler, the element might get clicked again causing a second attempt to resolve the promise, which will fail.
I have a working promise chain:
function startSync(db) {
var promise = new Promise(function(resolve, reject) {
syncCats(db)
.then(syncTrees(db))
.then(syncCars(db))
...
.then(resolve());
});
return promise;
}
This works great. It performs each of those function calls, waiting for each one to complete before firing off another. Each of those functions returns a promise, like so:
function syncCaesar(db) {
var promise = new Promise(resolve, reject) {
// do aysnc db calls, and whatnot, resolving/rejecting appropriately
}
return promise;
}
I need to run this chain on a series of databases, sequentially.
I've tried other solutions here, but they would fire off syncCats() all at once.
For instance:
var promise = Promise.resolve();
dbs.forEach(function(db) {
promise = promise.then(startSync(db));
}
Fires off syncCats(db[0]) and syncCats(db[1]) simultaneously.
Edit:
Performing startSync(dbs[0]).then(startSync(dbs[1])); acts the same.
Edit2: Also performs the same:
dbs.forEach(function(db) {
promise = promise.then(function() {
return startSync(db);
}
}
You're calling startSync and then passing its return value in to then. So naturally, if you do that twice, it's going to start the process twice in parallel.
Instead, pass in a function that doesn't call startSync until it's called:
var promise = Promise.resolve();
dbs.forEach(function(db) {
promise = promise.then(function() { startSync(db); });
});
or with ES2015:
let promise = Promise.resolve();
dbs.forEach(function(db) {
promise = promise.then(_ => startSync(db));
});
Separately, three things jump out about startSync:
It starts all its operations in parallel, not sequentially
It exhibits the promise creation anti-pattern. There's no reason for startSync to create a new promise; it already has a promise it can work with
It ensures that its resolution value is undefined
If you really want the operations running in parallel like that, I suggest being more explicit about it:
function startSync(db) {
return Promise.all([
syncCats(db),
syncTrees(db),
syncCars(db)
])
.then(_ => undefined); // This does #3
}
...but you could also do to avoid the anti-pattern:
// Still run in parallel!
function startSync(db) {
return syncCats(db)
.then(syncTrees(db))
.then(syncCars(db))
.then(_ => undefined); // Does #3
}
If you meant for them to be sequential, you need to pass functions, not the result of calling them:
function startSync(db) {
return syncCats(db)
.then(_ => syncTrees(db))
.then(_ => syncCars(db))
.then(_ => undefined); // Again, does #3
}
If you made syncCats, syncTrees, and syncCars resolve their promises with db, like this:
function syncCats(db) {
return startSomethingAsync().then(_ => db);
}
...then it could be:
function startSync(db) {
return syncCats(db)
.then(syncTrees)
.then(syncCars)
.then(_ => undefined); // Only here for #3
}
...since each would receive db as its first argument.
Finally, if you don't need to force undefined as the promise resolution value, I suggest dropping that part from the above. :-)
With appreciation to T.J and Jaromanda, I was able to solve it by fixing how startSync() was resolved:
function startSync(db) {
var promise = new Promise(function(resolve, reject) {
syncCats(db)
.then(syncTrees(db)) // not correct way to call, see below
.then(syncCars(db))
...
.then(function() { resolve(); });
});
return promise;
}
Now, as T.J points out, the way each of the .then's is being called is incorrect. He explains it better in his answer. I'm being saved by some misunderstood database layer queueing. syncTrees() and syncCars() should be running in parallel.
If I understood it correctly, you will be having an array of databases and you want to sync them one by one i.e. sync for dbs[0], once that is complete sync for dbs[1], and so on. If that's correct you can do something like this.
var syncMultipleDBs = (dataBases) {
var db = dataBases.shift();
if (!db) {
return;
}
startSync(db).then(syncMultipleDBs.bind(null, dataBases));
};
syncsyncMultipleDBs(dbs.slice());
I hope this will help.
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)
});