One Promise for Multiple Promises - Concurrency Issue - javascript

In the method "myMethod" of my "gulpfile.js" I want to create multiple Promises. The quantity depends on the array size (method parameter). When I call the method I want to make sure that all promises are fulfilled before I continue. I would prefer to return not before all promises are fulfilled.
Please have a look at the last five lines of code.
Dependencies
var promiseAll = require('gulp-all');
var del = require('del');
var deleteEmpty = require('delete-empty');
gulp-all | del | delete-empty
Helper Method
var oneForAllPromises = function(promises){
var promAll = promiseAll(promises);
promAll.then(function(param) {
console.log('foo');
}, function(err) {
console.error('foo');
});
return promAll;
}
Problematic Code
var myMethod = function(array1, array2){
var promise = del(array1, {force: true});
promise.then(paths => {console.log('foo');});
var promises = [];
promise.then(()=>{
for(var i=0; i<array2.length; i++){
promises[i] = new Promise(function(resolve, reject) {
deleteEmpty(array2[i], {force: true},
function(err, deleted){
if(err){
console.log('foo');
reject
}else{
console.log('foo');
resolve
}
}
);
});
}
});
// PROBLEM: Returns empty promises array
console.log("promiesesLENGTH: "+promises.length); // promiesesLENGTH: 0
// Create one promise for all the promises
return oneForAllPromises(promises);
}

At the time of the console.log, the first promise promise = del(array1, {force: true}); is not yet finished, so none of the code in the then is yet executed. That's why your promises are empty.
You can simply return in a then another promise:
var myMethod = function(array1, array2){
var promise = del(array1, {force: true});
return promise.then(() => {
return Promise.all(array2.map(array2value => {
return new Promise(function(resolve, reject) {
deleteEmpty(array2value, {force: true}, (err, deleted) => {
if (err) {
reject(err);
} else{
resolve()
}
});
});
}
});
}

Related

Pattern for dynamic Javascript promises

Inside a promise, I need to call and process an indeterminate number of asynch API responses after individually calling them either inside another promise, or after said promise, but before another so the order of execution is respected.
var promiseA = function() {
return new Promise(function(resolve, reject) {
// 1. Establish objects needed from one API endpoint
// 2. Call API endpoint for each object and parse
// 3. Only then continue to next promise
}
}
var finalPromise = function() {
return new Promise(function(resolve, reject) {
//
}
}
promiseA()
.then(finalPromise)
So inside promiseA, I find out how many objects I'll need to poll individually from an API. Each request is of course asynchronous. I need to make these calls and process the response before the final promise is called.
I am struggling to determine a pattern for this with promises, where I can dynamically create these promises and only allow the final promise to execute after the indeterminate and asynchronous have executed and processed. I've worked with other languages where this is possible, but I'm struggling to see it here with Promises.
Any help is appreciated.
I have changed the answer to incorporate the comments below. Since, you mentioned ES6 promises I shall stick to that. There are two basic types of callbacks that we might care about.
DOM load or other one time event callbacks (window.onload and so on)
Async method callback (AJAX call, setTimout and so on)
Since,
1.DOM load or other one time event
var p = new Promise(function(res, rej) {
window.onload = res();
};
2.Plain callback: these are callbacks that don't conform to a convention. e.g. setTimeout
var p = new Promise(function(res, rej){
setTimeout(function() {
//your business/view logic
success? res():rej(); //if successful resolve else reject
}, 2000);
});
In each of the above case the promise (var p) can be wrapped to be returned by a function.
var myAsyncMethod = function () {
var p = new ... // as mentioned in 1 or 2
return p;
}
Then the usage:
myAsyncMethod()
.then(function(){/* success-handler */})
.catch(function(/* failure-handler */));
Specific to your question you may have many such methods:
function baseAJAXCall (url) {
new Promise(functoin(rej, res) {
$.get(url, function(err, data){
if(err) {
rej();
}
else {
resolve(data);
}
});
}
};
function callAPIEndpoint(url) {
return baseAJAXCall(url);
}
function finalPromiseHandler () {
//your final business/view logic
}
//USAGE
callAPIEndpoint('/my-first-call')
.then(function(data){
var promiseArray = data.map(function(item){
return baseAJAXCall(item.url);
});
return Promise.all(promiseArray);
})
.then(finalPromiseHandler)
.catch(function(){
console.log('.error-message.');
});
Ref:
How do I convert an existing callback API to promises?.
http://www.datchley.name/es6-promises/
Links from comments below.
---OLD ANSWER: PLEASE OVERLOOK---
I am familiar with this library : https://github.com/kriskowal/q. And, you can do this using using the q.all and q.allSettled constructs. May be that is what you are looking for.
Normally, the pattern is to create a function that returns a promise.
function someAsyncFuncName1(url) {
var def = q.defer();
//async function
$.get(url, function(err, data){ //suppose
if(err){
def.reject();
}
else {
def.resolve(data); //pass the data to the .then() handler.
}
});
return def.promise;
}
function someAsyncFuncName2() {
var def = q.defer();
//async function
setTimeout(function(){ //suppose
//do something
if(good) {
def.resolve();
} else {
def.reject();
}
}, 1000); //arbitrary timeout of 1 second
return def.promise;
}
USAGE:
q.all([someAsyncFuncName1('/api-1'), someAsyncFuncName2()])
.then(function() {
//final handler
});
On a similar line of thought one can use q.allSettled() if you want to wait for all promises to return.
Hope this helps.
---EOF OLD ANSWER---
First of all, if async functions used in PromiseA don't return promises, you need to promisify them. You can do that with Promise constructor, but it's much better to use libraries, such as bluebird with their promisify methods.
Let's imagine, that we have two functions getUserIdsAsync and getUserAsync. The first on returns a list of user ids, getUserAsync returns an user data by userId. And you need to get a list of users by their ids. The code of PromiseA could look so:
var promiseA = function() {
return getUserIdsAsync()
.then(userIds => {
let ops = users.map(uid => getUserAsync(uid));
return Promise.all(ops);
});
}
The following snippet shows a solution without using any external library like bluebird. It follows the code snippet in your question (which seems to be more complicate than needed).
You have to collect all api promisses in an array. Then you can call Promise.all() to get a Promise for the end of all api promisses. Then you can do some final stuff, like parsing the result of each promise and continue afterwards.
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var apiEndpoint = function (name) {
return new Promise( (resolve, reject) => {
setTimeout(() => resolve('API ' + name + ' job done'), 1000);
});
}
var promiseA = function() {
return new Promise( (resolve, reject) => {
const promisses = [];
for (var i=1; i < getRandomInt(3,6); i++) {
// 1. Establish objects needed from one API endpoint
promisses.push(apiEndpoint('This is number ' + i));
}
Promise.all(promisses).then( results => {
// do final stuff
for (const s of results) {
// 2. Call API endpoint for each object and parse
console.log(s);
}
// continue ...
// 3. Only then continue to next promise
resolve('now it is finished');
}).catch( err => reject(err) );
});
}
var finalPromise = function() {
return new Promise( (resolve, reject) => {
console.log('finalPromise');
resolve();
});
}
promiseA()
.then( () => finalPromise())
.catch(err => console.log(err) );
Please hold in mind that this solution is not easy to read. Using external libraries or reducing promisses can improve readability. Maybe you should take a look to the async/await pattern to get a much more better (readable) solution.
Here is a solution with async/await:
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const apiEndpoint = function (name) {
return new Promise( (resolve, reject) => {
setTimeout(() => resolve('API ' + name + ' job done'), 1000);
});
}
async function promiseParallel () {
const promisses = [];
for (let i = 1; i < getRandomInt(3,6); i++) {
promisses.push(apiEndpoint('This is number ' + i));
}
for (const p of promisses) {
const x = await p;
console.log(x);
}
return ('everything is done');
}
promiseParallel().then( result => {
console.log(result);
}).catch( err => console.log(err) );
If you want call the promisses sequentially you can replace with:
async function promiseSequ () {
for (let i = 1; i < getRandomInt(3,6); i++) {
const x = await apiEndpoint('This is number ' + i);
console.log(x);
}
return ('everything is done');
}

Why returned promise is empty?

I am trying to use .all to do an action after all promise got resolved but when I return a promise its just empty.
I am appending all promises to pushPromise and later when I try to do Promise.all(pushPromise) then it wont work.
I tried to console.log(pushPromise) and it shows empty array. Why?
Thanks
var Client = require('ssh2-sftp-client');
var sftp = new Client();
var folderPromise = [];
var toServer = function(dir){
return new Promise(function (resolve, reject){
filePermission(dir, function(){
getAllFiles(dir.replace(/\/$/, ''));
console.log('Got all files');
resolve();
});
}).then(function(done){
arrayfolder.forEach(function(folder){
folder = folder.replace(dir, '');
//console.log(folder);
folderPromise.push(sftp.mkdir(folder, true));
});
return Promise.all(folderPromise).then(function(){
console.log('Folder added');
return 'Done';
});
}).then(function() {
return 'Done';
})
.catch((err) => {
console.log(err);
return 'Not Done';
});
}
var pushPromise = [];
connections.forEach(function(connection){
var dir = getConnectionDir(connection);
pushPromise.push(toServer(dir));
});
console.log(pushPromise); // return []
Promise.all(pushPromise).then(function(){
console.log('All uploaded to server');
});
Problem may be with connections returning an empty array. I tested locally and pushPromise is returning array of promises properly.

Cannot get $q.all with nested promises working - wait for all

I fully understand that similar questions have been asked before but I don't get it running. I need a chain of promises and I need to wait until all promises are resolved.
My issue is that I need to call different promises based on the outcome of another promise :-(
So based on the outcome of promise2 my promise4 is EITHER $translate.use OR it is $translate.refresh.
This is what I have so far (simplified):
var promise1 = someService.get({}).$promise.then(function (result) {
// do stuff
});
var promise2 = anotherService.getPromise().then(function (result) {
var properties = result[0];
// do stuff
return properties;
});
var promise3 = promise2.then(function(properties){
// using $translate (angular-translate) here which is async as well
var userLanguage = properties.language;
if (userLanguage !== $translate.preferredLanguage()) {
// either this is the 4th promise
$translate.use(userLanguage).then(function (myVar) {
// ...
});
} else {
// or this is the 4th promise
$translate.refresh().then(function (myVar) {
// ...
});
}
});
var loadPromises = {
promise1: promise1
promise2: promise2
promise3: promise3
promise4: ???
};
$q.all(loadPromises).then(function (result) {
// anything done
});
You don't really need to monitor promise3 and promise4, all you need is promise1 and promise2. promise3 becomes next step of the promise2. Just note how you return new promise (either return $translate.use or return $translate.refresh) from promise2 then part :
var promise1 = someService.get({}).$promise.then(function(result) {
// do stuff
});
var promise2 = anotherService.getPromise().then(function(result) {
var properties = result[0];
// do stuff
return properties;
})
.then(function(properties) {
// using $translate (angular-translate) here which is async as well
var userLanguage = properties.language;
if (userLanguage !== $translate.preferredLanguage()) {
// either this is the 4th promise
return $translate.use(userLanguage).then(function(myVar) {
// ...
});
} else {
// or this is the 4th promise
return $translate.refresh().then(function(myVar) {
// ...
});
}
});
var loadPromises = {
promise1: promise1,
promise2: promise2
};
$q.all(loadPromises).then(function(result) {
// anything done
});
If I understand your code well enough, I think you only need to return the inner promise inside of promise3. In your example, both promise1 and promise2 won't be resolved until the service is done. Since promise3 depends on promise2 it won't finish until promise2 resolved. I believe $q.all keeps going until all promises are complete, even promises returned by promises. Since promise3 returns a promise, promise3 won't be considered resolved until the inner promise is resolved. So, I think adding a couple return statements is all that you need:
var promise1 = someService.get({}).$promise.then(function (result) {
// do stuff
});
var promise2 = anotherService.getPromise().then(function (result) {
var properties = result[0];
// do stuff
return properties;
});
var promise3 = promise2.then(function(properties){
// using $translate (angular-translate) here which is async as well
var userLanguage = properties.language;
if (userLanguage !== $translate.preferredLanguage()) {
// either this is the 4th promise
//NOTE: added return here
return $translate.use(userLanguage).then(function (myVar) {
// ...
});
} else {
// or this is the 4th promise
//NOTE: added return here
return $translate.refresh().then(function (myVar) {
// ...
});
}
});
var loadPromises = {
promise1: promise1,
promise2: promise2,
promise3: promise3
};
$q.all(loadPromises).then(function (result) {
// anything done
});

Node return BluebirdJS Promise

I have a small problem, this script works perfectly, with one problem, the "runTenant" method is not returning a promise (that needs resolving from "all()".
This code:
Promise.resolve(runTenant(latest)).then(function() {
end();
});
Calls this code:
function runTenant(cb) {
return new Promise(function() {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
new Tenant().fetchAll()
.then(function(tenants) {
if (tenants.models.length == 0) {
return;
} else {
async.eachSeries(tenants.models, function(tenant, next) {
var account = tenant.attributes;
Promise.resolve(db_tenant.config(account)).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
Promise.resolve(knex_pending).then(function() {
next(null, null);
});
} else {
next(null, null);
}
});
});
};
});
});
}
The code from runTenant is working correctly however it stalls and does not proceed to "end()" because the promise from "runTenant(latest)" isn't being resolved.
As if it weren't apparent, I am horrible at promises. Still working on getting my head around them.
Many thanks for any help/direction!
You should not use the Promise constructor at all here (and basically, not anywhere else either), even if you made it work it would be an antipattern. You've never resolved that promise - notice that the resolve argument to the Promise constructor callback is a very different function than Promise.resolve.
And you should not use the async library if you have a powerful promise library like Bluebird at hand.
As if it weren't apparent, I am horrible at promises.
Maybe you'll want to have a look at my rules of thumb for writing promise functions.
Here's what your function should look like:
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll().then(function(tenants) {
// if (tenants.models.length == 0) {
// return;
// } else
// In case there are no models, the loop iterates zero times, which makes no difference
return Promise.each(tenants.models, function(tenant) {
var account = tenant.attributes;
return db_tenant.config(account).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
return cb(knex_tenant); // can return a promise
}
});
});
});
}
Your promise in runTenant function is never resolved. You must call resolve or reject function to resolve promise:
function runTenant() {
return new Promise(function(resolve, reject) {
// somewhere in your code
if (err) {
reject(err);
} else {
resolve();
}
});
});
And you shouldn't pass cb in runTenant function, use promises chain:
runTenant()
.then(latest)
.then(end)
.catch(function(err) {
console.log(err);
});
You need to return all the nested promises. I can't run this code, so this isn't a drop it fix. But hopefully, it helps you understand what is missing.
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll() //added return
.then(function (tenants) {
if (tenants.models.length == 0) {
return;
} else {
var promises = []; //got to collect the promises
tenants.models.each(function (tenant, next) {
var account = tenant.attributes;
var promise = Promise.resolve(db_tenant.config(account)).then(function (knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
return knex_pending; //return value that you want the whole chain to resolve to
}
});
promises.push(promise); //add promise to collection
});
return Promise.all(promises); //make promise from all promises
}
});
}

Promise.all() resolving, but it shouldn't (node.js)

I'm trying something like this right now in node.js:
var exec = require('child_process').exec
var write = require('fs-writefile-promise')
function run() {
var myArray = [];
var execs = [];
for (var i = 1; i <= 7; i++) {
(function(cntr) {
write('file-' + i + '.txt', someString)
.then(function (filename) {
execs.push(new Promise(function(resolve, reject) {
exec('cat ' + 'file-' + cntr + '.cnf', function(error, stdout, stderr) {
console.log(cntr + ' ' + stdout);
if (stdout.search(/\bsomeString\b/) > -1) {
myArray.push(cntr);
resolve();
}
else {
resolve();
}
})
}))
})
.catch(function (err) {
console.error(err);
});
})(i);
}
return Promise.all(execs).then(function() {
return new Promise(function(resolve) {
resolve(myArray);
})
})
}
run().then(function(result) {
console.log(result);
});
As you can see, I'm creating multiple Promises that run exec() and each one of them resolves when exec() finishes.
Then I'm waiting for every Promise to resolve in Promise.all(execs) to return myArray as a Promise. Yet, when I execute my run() function at the end, it is returning an empty array. I guess this has something to do with Promise.all() as it resolves even if some Promises in execs haven't resolved yet, but I'm not sure, thats why I really need some help here. Does anyone know where I'm making a mistake in code?
Thank you very much in advance!
#EDIT 1
var exec = require('child_process').exec
var write = require('fs-writefile-promise')
function run() {
var myArray = [];
var execs = [];
for (var i = 1; i <= 7; i++) {
(function(cntr) {
return new Promise(function(resolve, reject) {
fs.writeFile('file-' + i + '.txt', someString, (err) => {
if (err) {
reject();
}
else {
resolve();
}
});
})
.then(function (filename) {
execs.push(new Promise(function(resolve, reject) {
exec('cat ' + 'file-' + cntr + '.cnf', function(error, stdout, stderr) {
console.log(cntr + ' ' + stdout);
if (stdout.search(/\bsomeString\b/) > -1) {
myArray.push(cntr);
resolve();
}
else {
resolve();
}
})
}))
})
.catch(function (err) {
console.error(err);
});
})(i);
}
return Promise.all(execs).then(function() {
return new Promise(function(resolve) {
resolve(myArray);
})
})
}
run().then(function(result) {
console.log(result);
});
There are numerous issues with both your attempts. The issue with the first attempt is that you're populating the execs array after an asynchronous operation so it has nothing in it when you actually pass the array to Promise.all(), thus Promise.all() has nothing to wait for.
In addition, you're not just using promises that are already created, thus you end up making way more promises than needed.
In general, it is best to "promisify" your async operations once outside of your main logic and then have all your logic be promise driven rather than mixing and matching promises with plain callbacks. Here's a version that attempts to fix those issues:
var exec = require('child_process').exec
var write = require('fs-writefile-promise')
// make promisified version of exec
function execP(file, options) {
return new Promise(function(resolve, reject) {
exec(file, options, function(err, stdout, stderr) {
if (err) return resolve(err);
resolve({stdout: stdout, stderr: stderr});
});
});
}
function run() {
var promises = [];
for (var i = 1; i <= 7; i++) {
promises.push(write('file-' + i + '.txt', someString).then(function(filename) {
return execP(filename);
}));
}
return Promise.all(promises).then(function(results) {
// results is an array of {stdout: xxx, stderr: yyy} objects
// process those results into a new array of just indexes
var final = [];
results.forEach(function(data, index) {
if (data.stdout.search(/\bsomeString\b/) > -1) {
final.push(index);
}
});
return final;
});
}
run().then(function(results) {
// array of indexes that contained the desired search string
}, function(err) {
// process error here
});
Note: This runs all your exec operations in parallel which is what your original code did. If you want to run them sequentially, that could be done too, but would require some tweaks.
As write is asynchronous, the program at this stage is passing back to the main thread and that's going straight through to your promise.all and returning that before execs has been loaded.
I suggest you create a function that returns a promise of both a file save followed by exec.

Categories