While loop in promise until another promise ends - javascript

I need to test with REDIS (which work asynchronously) if a value is set or not before letting my code to run.
I started to work with Loop Promise as defined here : https://stackoverflow.com/a/17238793/3912805
I've tried this but unfortunately I'm stuck with a promise in pending :
promise: { state: 'pending' }
function promiseWhile(condition, body) {
var done = Q.defer();
function loop() {
/** When the result of calling condition is no longer true, we are done */
if(!condition())
return done.resolve();
/** Use 'when', in case `body` does not return a promise */
/** When it completes loop again otherwise, if it fails, reject the done promise */
Q.when(body(), loop, done.reject);
}
/** Start running the loop in the next tick so that this function is completely async */
/** It would be unexpected if body was called synchronously the first time */
Q.nextTick(loop);
return done.promise;
}
var timeline_id = 3;
promiseWhile(function () {
return Q.ninvoke(redisClient, 'hget', 'hashTest', timeline_id).then(function (result) {
console.log(result + '<---->');
return result;
});
}, function () {
return Q.delay(500);
}).then(function () {
// Let's continue process...
});
I need to check in intervals, cause if a timeline_id is already in process, I need to wait UNTIL timeline_id has been removed on Redis from hashTest.
How can I be sure I got result rather than promise state to check if I can still run my loop or not.

Finally, I found a way to achieve this...
for sure not really elegant, but I can't do better for now :
var redisState = true;
promiseWhile(function () {
/** Loop until true */
return redisState;
}, function (result) {
return Q.ninvoke(redisClient, 'HGET', 'hashTest', timeline_id).then(function(result) {
redisState = result;
if (redisState) {
redisState = result;
return Q.delay(500);
}
});
}).then(function() {
...
}):

Related

How to create a queue of promises that will be sync when it's empty?

I have this problem in the jQuery Terminal library. I have an echo method that prints the stuff on the terminal and you can print a string, promise, or function that returns a promise or string (to simplify let's assume string or promise).
But the issue is that if you echo a few promises and strings they are not printed in order. The code was just waiting with the next echo until the promise was resolved. The problem is that it only works for one promise.
So I was thinking that I need a kind of data structure that will keep adding promises and it will wait for all promises. But I'm not sure how to do this.
The problem I have is that I can't just chain promises because the echo method needs to be synchronous when there is nothing in a queue and you print a string. But this is not how Promise A+ behaves they are always async (even Promise.resolve()). I have a lot of unit tests that rely on echo being synchronous and it will be break change and I don't want that.
My idea was to just create an array of promises but I'm not sure when I should remove the promise from the array so the queue can be empty when all promises are resolved and I can do synchronous call.
Something like:
class PromiseQueue {
constructor() {
this._promises = [];
}
add(promise) {
this._promises.push(promise);
}
empty() {
return !this._promises.length;
}
then(fn) {
if (this.empty()) {
fn();
} else {
Promise.all(this._promises).then(function() {
// what do do with this._promises?
fn();
});
}
}
}
I guess it's not that simple as in my attempt. I have no idea how to implement this behavior.
EDIT:
I have this two cases that I want to handle:
function render(text, delay) {
return new Promise(resolve => {
setTimeout(() => resolve(text), delay);
});
}
term.echo(() => render('lorem', 1000));
term.echo('foo');
term.echo(() => render('ipsum', 1000));
term.echo('bar');
term.echo(() => render('dolor', 1000));
term.echo('baz');
setTimeout(function() {
// this should render immediately because all promises
// would be already resolved after 5 seconds
term.echo('lorem ipsum');
// so after the call I check the DOM and see the output
// this is important for unit tests and will be huge
// breaking change if echo don't render string synchronously
}, 5000);
NOTE: echo promise and function that return a promise in this example is the same the only difference is that function is re-invoked in each re-render (e.g. when browser or container is resized).
Another example is just:
term.echo('foo');
term.echo('bar');
term.echo('baz');
that should be also synced. I need a generic solution so you don't need to know exactly what echo is doing.
I would not even use Promise.all here - wait only for the first promise in the queue.
const term = {
/** an array while a promise is currently awaited, null when `echo` can be synchronous */
_queue: null,
echo(value) {
if (this._queue) {
this._queue.push(value);
} else {
this._echo(value);
}
},
/** returns a promise if the `value` is asynchronous, undefined otherwise */
_echo(value) {
try {
if (typeof value == "function") {
value = value();
}
if (typeof value.then == "function") {
this._queue ??= [];
return Promise.resolve(value).then(console.log, console.error).finally(() => {
while (this._queue.length) {
if (this._echo(this._queue.shift())) {
return;
}
}
this._queue = null;
});
} else {
console.log(value);
}
} catch(err) {
console.error(err);
}
}
};
function render(text, delay) {
return new Promise(resolve => {
setTimeout(() => resolve(text), delay);
});
}
term.echo('foo');
term.echo(() => render('lorem', 1000));
term.echo('bar');
term.echo(() => render('ipsum', 1000));
term.echo('baz');
term.echo(() => render('dolor', 1000));
term.echo('quam');
setTimeout(function() {
// this should render immediately because all promises
// would be already resolved after 5 seconds
term.echo('lorem ipsum');
console.log('end');
}, 5000);
console.log('echos queued');
While I was editing the question I've realized that this is similar to exec behavior:
term.exec('timer --time 1000 "hello"');
term.exec('echo world');
term.exec('timer --time 1000 "hello"');
term.exec('echo world');
and I solve this using same mechanism that was proved to work.
I've added a flag:
if (is_promise(next)) {
echo_promise = true;
}
similar to paused flag.
Then when promise next is resolved. I used the same what was done in resume()
unpromise(next, function() {
echo_promise = false;
var original = echo_delay;
echo_delay = [];
for (var i = 0; i < original.length; ++i) {
self.echo.apply(self, original[i]);
}
});
unpromise is a function that I always use. It invokes the function as then callback or calls immediately. So it's sync by default but async when needed. You can find the code on GitHub jquery.terminal-src.js#L1072.
And then last thing in echo main code:
if (echo_promise) {
echo_delay.push([arg, options]);
} else {
echo(arg);
}
This is not very clean code because echo is invoked multiple times but it works. If you know a better solution please share.
Maybe this kind of code can be abstracted into single PromiseQueue interface.

Wait for all nested promises to complete, but still react to each individual resolve

Suppose that newsService.getNews() returns a promise that should resolve to a random news entry returned by some service, while translateService.translate() returns a promise that should resolve to the translation of the passed text.
var newsPromises = [];
var translatePromises = [];
for (var i = 0; i < 5; i++) {
var p1 = this.newsService.getNews();
newsPromises.push(p1);
p1.then(function (data) {
var p2 = this.translateService.translate(data);
translatePromises.push(p2);
p2.then(function (translatedData) {
addNews(`${data} (${translatedData})`);
}, function (fail) {
console.log(fail.message);
});
}, function (fail) {
console.log(fail.message);
});
}
now the page initially shows a loading spinner that I would like to hide when all the promises (including the nested translation promises) have completed (succeeded or failed):
Promise.all(newsPromises)
.then(function (results) {
Promise.all(translatePromises).then(function (results) {
removeLoading();
},
function (err) {
removeLoading();
}
);
}, function (err) {
Promise.all(translatePromises).then(function (results) {
removeLoading();
},
function (err) {
removeLoading();
}
);
});
This code a) does not work as it should, since the loading spinner some times disappears before the promises resolve, and b) is horribly complex.
How is this done properly? (with vanilla JS / ES6)
Remember that promises chains are pipelines, where each handler can transform the chain's result as the result passes through the handler. See comments:
// We only need one array of promises
const promises = [];
// Build the array
for (let i = 0; i < 5; i++) {
// Add this promise to the array
promises.push(
// Get the news...
this.newsService.getNews().then(
// ...and translate it...
data => this.translateService.translate(data)
.then(translatedData => {
// ...and show it as soon as it's available
addNews(`${data} (${translatedData})`);
// Note that here we're converting the resolution value to
// `undefined`, but nothing uses it so...
// If you want something to be able to use it,
// return `translatedData` (or `data` or...)
})
)
.catch(fail => {
console.log(fail.message);
// WARNING: Here you're converting rejection to resolution with `undefined`
})
);
}
// Wait until all that is done before removing the loading indicator
Promise.all(promises).then(removeLoading);
Note that the only reason we don't need a catch on the Promise.all promise is that you're ignoring (other than logging) errors that occur, so we know that promise will never reject.
Also note that the above assumes removeLoading doesn't pay any attention to the arguments it receives, and that it doesn't return a promise that may reject. If it does care about arguments and it's important to call it with no arguments, change the Promise.all bit to:
Promise.all(promises).then(() => removeLoading());
If it returns a promise that may reject, you'll need a catch handler as well.
in such cases i creating global counter loadersCount = 0
each time you call this.newsService.getNews() call function loaderStart()
and each time you call addNews() or console.log(fail.message) call loaderStop()
function loaderStart () {
if (loadersCount === 0) {
addLoading();
}
loadersCount++;
}
function loaderStop () {
if (loadersCount === 1) {
removeLoading();
}
loadersCount--;
}

Do something only after asynchronous call is finished

I'm trying to create a bounce effect on an image after an synchronous AND an asynchronous call but can't figure out how to do it. The problem I'm having now is that I sometimes get both the bounce effect and afterwards isExecuted becomes true because the asynchronous event takes some time.
My code should work like this:
Iterate over each object in myEnum and execute the following
if myCondition1 is equal too myCondition2 set isExecuted = true
if above is not true, call an asynchronous method which evaluates some stuff, if it's true it will set isExecuted = true.
wait for all above is finished, then if isExecuted still is false, bounce the image.
Here's the code:
var isExecuted = false;
myEnum.each()
{
if (myCondition1 == myCondition2) { //Synchronous
isExecuted = true;
return false; //stop the iteration
}
else {
MyAsyncFunction(myCondition2, function(isTrue) { // Asynchronous
if (isTrue) {
isExecuted = true;
return false; //stop the iteration
}
});
}
});
// Execute this ONLY when above code is finished executing
if (isExecuted == false) {
BounceImage();
}
Please note that the async function is not always executed but the bounce check must always be executed if isExecuted is true.
This whole setup won't work as you want because you cannot stop the iteration from the asynchronous callback. Instead you have to process the array (or whatever myEnum is) asynchronously and do something afterwards. I highly recommend to learn about promises.
function process(array, cb) {
var i = 0;
function p(v) {
return new Promise((resolve, reject) => {
try {
// call the callback, passing in the current value and
// a callback to control execution
cb(v, function next(stop, result) {
if (stop) {
// if stop = true, abort the iteration and resolve the passed in value
resolve(result);
} else if (i < array.length) {
// else if there are more elements left, process them
resolve(p(array[i++]));
} else { // else resolve to the passed in value
resolve(result);
}
});
} catch(e) {
reject(e);
}
});
}
// start with the first element
return p(array[0]);
}
process([1,2,3], function(v, next) {
if (v == 2) {
return next(true, v);
}
next();
}).then(result => console.log(result));
Applied to your code it would look something like
process(myEnum, function(v, next) {
if (myCondition1 == myCondition2) {
return next(true, true);
} else {
MyAsyncFunction(myCondition2, function(isTrue) {
if (isTrue) {
return next(true, true);
}
next();
});
}
}).then(function(isExecuted) {
if (!isExecuted) {
BounceImage();
}
});
Of course you can also use an existing library that allows you to do this. There a many different (potentially more elegant) ways to achieve this.
Instead, use callbacks:
function asyncFunction (a, b, c, callback) {
...
callback();
}
asyncFunction(1, 2, 3, function () {
doSomethingOnceDone();
});
This is a very common practice. It's how most async Chrome APIS do it, just to name one thing.

Javascript promise not delaying function execution

I'm using ES6 javascript promises in Chrome and am having trouble understanding why the promise executed within the function _getStatus() is not returning the result argument in the success handler which would result in the alert box containing "done". Instead, I get an alert box that says "undefined".
myNameSpace = function(){
var obj = {
groupA: {
status: "inactive"
},
groupB: {
status: "active"
}
};
function _getStatus(group){
_finishTask().then(function(result){
return result; // doesn't return anything
});
}
function _finishTask(){
var promise = new Promise(function(resolve, reject){
// do some task before resolving
resolve("done");
});
return promise;
};
return{
getStatus:_getStatus
}
}();
$(function(){
alert(myNameSpace.getStatus("groupA")); // why is this "undefined" instead of "done"?
});
Because this is not how Promises work. You need to make sure both _getStatus and _finishTask return Promise objects. Then you will be able to use those promises API methods to execute subsequent code what promise is resolved.
So your code should look something like this:
myNameSpace = function() {
var obj = {
groupA: {
status: "inactive"
},
groupB: {
status: "active"
}
};
function _getStatus(group) {
return _finishTask().then(function(result) {
return result + " and tested";
});
}
function _finishTask() {
return new Promise(function(resolve, reject) {
// do some task before resolving
resolve("done");
});
};
return {
getStatus: _getStatus
}
}();
myNameSpace.getStatus("groupA").then(alert);
Finally, regarding this construction:
return _finishTask().then(function(result) {
return result;
});
_finishTask returns a Promise object, when this promise is resolved you get into then callback. Whatever value you return from this callback becomes a new resolution value for the subsequent success callbacks down the resolution chain.
Demo: http://plnkr.co/edit/K1SWKuTYA3e46RxdzkCe?p=preview
You cant return a result from an asynchronous function as the code running it has already finished by the time the response is returned.
You can however pass in a callback to execute once the code is complete:
function _getStatus(group, callback){
_finishTask().then(function(result){
callback(result);
});
}
$(function(){
myNameSpace.getStatus("groupA", function(result) {
alert(result);
});
});
or use the promise api itself (in this case your _getStatus method is a little redundant):
function _getStatus(group){
return _finishTask();
}
$(function(){
myNameSpace.getStatus("groupA").then(function(result) {
alert(result);
});
});

Make angular.forEach wait for promise after going to next object

I have a list of objects. The objects are passed to a deferred function. I want to call the function with the next object only after the previous call is resolved. Is there any way I can do this?
angular.forEach(objects, function (object) {
// wait for this to resolve and after that move to next object
doSomething(object);
});
Before ES2017 and async/await (see below for an option in ES2017), you can't use .forEach() if you want to wait for a promise because promises are not blocking. Javascript and promises just don't work that way.
You can chain multiple promises and make the promise infrastructure sequence them.
You can iterate manually and advance the iteration only when the previous promise finishes.
You can use a library like async or Bluebird that will sequence them for you.
There are lots of different alternatives, but .forEach() will not do it for you.
Here's an example of sequencing using chaining of promises with angular promises (assuming objects is an array):
objects.reduce(function(p, val) {
return p.then(function() {
return doSomething(val);
});
}, $q.when(true)).then(function(finalResult) {
// done here
}, function(err) {
// error here
});
And, using standard ES6 promises, this would be:
objects.reduce(function(p, val) {
return p.then(function() {
return doSomething(val);
});
}, Promise.resolve()).then(function(finalResult) {
// done here
}, function(err) {
// error here
});
Here's an example of manually sequencing (assuming objects is an array), though this does not report back completion or errors like the above option does:
function run(objects) {
var cntr = 0;
function next() {
if (cntr < objects.length) {
doSomething(objects[cntr++]).then(next);
}
}
next();
}
ES2017
In ES2017, the async/wait feature does allow you to "wait" for a promise to fulfill before continuing the loop iteration when using non-function based loops such as for or while:
async function someFunc() {
for (object of objects) {
// wait for this to resolve and after that move to next object
let result = await doSomething(object);
}
}
The code has to be contained inside an async function and then you can use await to tell the interpreter to wait for the promise to resolve before continuing the loop. Note, while this appears to be "blocking" type behavior, it is not blocking the event loop. Other events in the event loop can still be processed during the await.
Yes you can use angular.forEach to achieve this.
Here is an example (assuming objects is an array):
// Define the initial promise
var sequence = $q.defer();
sequence.resolve();
sequence = sequence.promise;
angular.forEach(objects, function(val,key){
sequence = sequence.then(function() {
return doSomething(val);
});
});
Here is how this can be done using array.reduce, similar to #friend00's answer (assuming objects is an array):
objects.reduce(function(p, val) {
// The initial promise object
if(p.then === undefined) {
p.resolve();
p = p.promise;
}
return p.then(function() {
return doSomething(val);
});
}, $q.defer());
check $q on angular:
function outerFunction() {
var defer = $q.defer();
var promises = [];
function lastTask(){
writeSome('finish').then( function(){
defer.resolve();
});
}
angular.forEach( $scope.testArray, function(value){
promises.push(writeSome(value));
});
$q.all(promises).then(lastTask);
return defer;
}
The easiest way is to create a function and manually iterate over all the objects in the array after each promise is resolved.
var delayedFORLoop = function (array) {
var defer = $q.defer();
var loop = function (count) {
var item = array[count];
// Example of a promise to wait for
myService.DoCalculation(item).then(function (response) {
}).finally(function () {
// Resolve or continue with loop
if (count === array.length) {
defer.resolve();
} else {
loop(++count);
}
});
}
loop(0); // Start loop
return defer.promise;
}
// To use:
delayedFORLoop(array).then(function(response) {
// Do something
});
Example is also available on my GitHub:
https://github.com/pietervw/Deferred-Angular-FOR-Loop-Example
I use a simple solution for a connection to a printer that wait till the promise is over to go to the next.
angular.forEach(object, function(data){
yourFunction(data)
.then(function (){
return;
})
})
It might help someone as I tried several of above solution before coming up with my own that actually worked for me (the other ones didn't)
var sequence;
objects.forEach(function(item) {
if(sequence === undefined){
sequence = doSomethingThatReturnsAPromise(item)
}else{
sequence = sequence.then(function(){
return doSomethingThatReturnsAPromise(item)
});
}
});
It worked for me like this. I don't know if it is a right approach but could help to reduce lines
function myFun(){
var deffer = $q.defer();
angular.forEach(array,function(a,i) {
Service.method(a.id).then(function(res) {
console.log(res);
if(i == array.length-1) {
deffer.resolve(res);
}
});
});
return deffer.promise;
}
myFun().then(function(res){
//res here
});

Categories

Resources