I have a standard JavaScript project. It is mostly synchronous with a few callbacks. I needed to use a 3rd party library to simplify things. The issue is that this library is based on an async approach and Promises. I have never really used this approach before.
I have a situation where I just need something = get_info(); The code is all very modular, so there are multiple function calls stacked up. The problem is this get_info is async. I just can't figure out how to use this async function withing my project without having to rewrite the whole thing.
Edit: Here is an example of similar code
function my_class() {
this.do_something( data ) {
if ( this.get_data() == data ) {
do_the_thing();
}
}
this.get_data = function() {
return library.get_info( arguments ); //can't do this, returns a promise
}
}
var stuff = new my_class();
for ( var i = 0; i < len; i++ ) {
stuff.do_something( i )
}
Again, the goal is to not rewrite this entire part of the application. Thoughts?
There is no need to promisify every function that gets called in the chain. You can call either synchronous or asynchronous functions as much as you want within the chain of the promises. All you have to think about is where to wait for the resolved value.
Your code isn't far off from working, a simple addition of a .then() block makes it work.
Working example below:
function my_class() {
this.do_something(data) {
//Call the promise-returning function
this.get_data()
.then((resolvedData) => {
//Wait for promise resolution
//and compare the resolved value to 'data'
if (resolvedData === data) {
do_the_thing();
}
})
.catch();
}
this.get_data = () => {
return library.get_info(arguments);
}
}
var stuff = new my_class();
for (var i = 0; i < len; i++) {
stuff.do_something(i)
}
How does it work?
do_something(i) is called
this.get_data() is called from inside the do_something() function.
When the promise that get_data() returns has been resolved, execution continues into then .then() block.
The resolvedData is the resolved value from the get_data() promise.
Compare the values, and continue.
As you can see, this does not take into consideration that the for-loop will continue even though the call to stuff.do_something(i) hasn't completed.
It is unclear in your question if you allow them all to run in parallel (like now) or if you need that they run in sequence.
If you want to to know if all calls are done, you can let do_something() return a promise and then wait for them all to resolve using Promise.all(), like below
function my_class() {
this.do_something(data) {
//Call the promise-returning function
//Note that we return the same promise - no need to create a new one!
return this.get_data()
.then((resolvedData) => {
//Wait for promise resolution
//and compare the resolved value to 'data'
if (resolvedData === data) {
do_the_thing();
}
})
.catch();
}
this.get_data = () => {
return library.get_info(arguments);
}
}
var stuff = new my_class();
//
var promises = [];
for (var i = 0; i < len; i++) {
//Push each promise to the array
promises.push(stuff.do_something(i));
}
Promise.all(promises)
.then(() => {
console.log("All promises have been resolved!")
})
.catch(() => {
//Handle errors
});
function my_class() {
this.do_something( data ) {
return new Promise(function(resolve,reject){
this.get_data().then(function(dataReceiced){
//reading promise retuned by get_data();
if(data == dataReceiced){
do_the_thing(); //need to promisify this also to use then();
resolve('Done');
} else { reject('Error'); }
});
});
}
}
this.get_data = function() {
return library.get_info( arguments ); //can't do this, returns a promise
}
}
i=0; // keep i global
var stuff = new my_class();
function doStuff(){
if(i < len){ mainStuff(); }
}
function mainStuff(){
stuff.do_something( i ).then(function(){
i++;
doStuff();
});
};
doStuff(); // to start process.
You need to promisify every function involved in the chain ..
Something like this..
Not sure about the complete scenario , code will look something like this.
Related
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.
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--;
}
I currently have some jQuery code that looks a bit like this:
for ( i = 0; i < limitVar; i++ ) {
doAjaxStuff(i);
}
function doAjaxStuff( i ) {
Here we make a SYNCHRONOUS ajax call, sending i.
}
The ajax call needs to be synchronous - one isn't fired until the last one is done.
As synchronous JS is deprecated, I want to move this code to use promises. How would I achieve this? I've been unable to find an example that close enough fits this situation.
You don't do synchronous ajax in the browser (well technically, you can in some circumstances, but it's a really bad idea to do so because it locks up the browser during the ajax call).
Instead, you redesign your loop so that it only carries out the next ajax call when the previous one is done which means you have to loop manually, you can't use a for loop. Since your code is pseudo-code (you don't show the real ajax operation), I'll use a jQuery ajax example, but you can substitute any ajax function you have as long as it either returns a promise or uses a callback to signal when its done.
The general idea is that you create a function for your ajax call and you use the completion callback from that to increment your index and then run the next iteration of your loop.
function runLoop(data) {
var i = 0;
function next() {
if (i < data.length) {
return $.ajax(data[i]).then(function(data) {
++i;
return next();
});
else {
// all done with loop
}
}
return next();
}
// call it like this
runLoop(someArray).then(function() {
// all done here
});
If you don't have an array of data, but just want a loop index:
function runLoop(limitVar) {
var i = 0;
function next() {
if (i < limitVar) {
return $.ajax(something_with_i_in_it).then(function(data) {
++i;
return next();
});
else {
// all done with loop
}
}
return next();
}
// call it like this
runLoop(theLimit).then(function() {
// all done here
});
If your limitVar is not large and there is no other logic involved in deciding whether to continue the loop, you can also use a little bit simpler pattern if you have an ajax function that returns a promise:
function runLoop(limitVar) {
var p = Promise.resolve();
for (var i = 0; i < limitVar; i++) {
p = p.then(function(prevResult) {
return someAjax(i);
});
}
return p;
}
// call it like this
runLoop(theLimit).then(function() {
// all done here
});
If you aren't using ajax functions that return a promise, then it's only a few lines of code to wrap your function with one that does and then you can more easily use these design patterns.
Process the array using some separate function. Each time you take off another element from array, then process it and when it's done call the function again. If there is no more item in list then the whole process is done.
var listOfRequests = ...;
new Promise( function( resolve, reject ) {
requestNext();
function requestNext() {
if ( !listOfRequests.length ) {
return resolve();
}
var next = listOfRequests.shift();
doAjaxStuff( next, reject, requestNext );
}
} )
doAjaxStuff( request, errCallback, doneCallback ) {
...
}
this is a pretty simple pattern:
var queue = Promise.resolve();
var nop = () => null;
for(let i=0; i<limitVar; ++i){
queue = queue.then(() => doAjaxStuff(i));
//or if you want to ignore Errors
//queue = queue.then(() => doAjaxStuff(i)).catch(nop);
}
queue.then(() => console.log("finished"));
Or if you use an Array as input:
var done = data.reduce(
(queue, value, index) => queue.then(() => doSomethingWith(value, index)),
Promise.resolve()
);
done.then(() => console.log("finished"));
I'm rewriting some database code from synchronous (LocalStorage) to asynchronous (IndexedDB). I'm using the Alasql library and Promises. One of the problems I encounter is that when doing things asynchronously, sometimes it seems impossible to avoid duplicating code.
For example, my synchronous (pseudo) code could be something like this (idExists, doUpdate and doInsert are database methods):
function insertOrUpdate(data,id)
{
var result = null;
if (!idExists(id)) // idExists returns a boolean
result = doInsert(data,id); // doInsert returns an object
else
result = doUpdate(data,id); // doUpdate returns an object
doSomething(result);
}
With asynchronous code, it becomes something like this:
function insertOrUpdate(data,id)
{
var promise1 = idExists(id); // idExists returns a promise
promise1.then( function(id_exists) {
if (id_exists) {
var promise2 = doInsert(data,id); // doInsert returns a promise
promise2.then( function(result) {
doSomething(result);
});
}
else {
var promise3 = doUpdate(data,id); // doUpdate returns a promise
promise3.then( function(result) {
doSomething(result);
});
}
});
}
Here I have to call doSomething at two places in the code. Is there a way to avoid this situation? I'm new to promises and my apologies if this has been asked before, but I couldn't find an answer to this.
You can return a promise from a chained callback, which then gets inserted into the promise chain. Your code can, and should, be written as:
function insertOrUpdate(data, id) {
return idExists(id)
.then(function (exists) {
return exists ? doInsert(data, id) : doUpdate(data, id);
})
.then(doSomething);
}
The promises from doInsert or doUpdate will be chained into the existing chain from idExists, so the final .then(doSomething) will be executed with their result.
You can store promise into variable and call doSomething only once:
function insertOrUpdate(data, id) {
return idExists(id).then(function(id_exists) {
var promise = id_exists ? doInsert(data, id) : doUpdate(data, id)
return promise.then(doSomething)
});
}
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
});