I'm trying to get the basics of how to implement promises and Promise.All within a given array loop, for example, from a firebase query, which can perform any number of actions within the loop, and then I have access to the results later. I can't seem to get the correct syntax, logic . below is a sample method in which I'd like to delay insert some items into an array and then have access to the array after the loop for processing.
I've researched different methods on Stackoverfllow. Some assign a promises = the snap.forEach loop and then somehow resolve this after completion. Others are creating a promise inside the loop. In my example below, I'm just using the settimeout to delay/create an async process.
testfirebaseData = () => {
let channels =[];
var promises=[];
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
snap.forEach(channelChild => {
promises.push(new Promise((resolve) => {
setTimeout(() => {
channels.push(channelChild.key);
resolve(channels)
},1000)
})
)
})
}).then (() => {
Promise.all(promises).then(() => {
console.log(channels[0])
})
}
}
I would expect the output of the above to show the first element in the "channels" array, but it's always coming back with "undefined"..as I clearly am not processing the promises correctly. What am i missing?
Here is sample example using your code style. Instead of objects within the array I've put numbers;
https://jsfiddle.net/th3vecmg/4/
var promises = [];
let channels =[];
[1, 2, 3, 4, 5].forEach((elm, i) => {
promises.push(new Promise((resolve) => {
setTimeout(() => {
channels.push(elm)
resolve(elm);
}, 1000)
}))
});
console.log(channels[0]) // gives undefined
Promise.all(promises).then(() => {
console.log(channels[0]) // gives 1
})
You should not be getting undefined inside the then because it's waiting for the promise to execute. You will only get undefined it you are trying to display the result outside the then scope
The code doesn't make really sense since you are displaying a global variable inside the then rather then relying on the output of the promise
If you want to delay execution by 1 then do some multiplication in the timeArgument of the setTimeout...
Your new Promise(...) should wrap the firebase call as that is the async operation. And not be in the callback. There is no async operations going on inside the callback just plain array push which is a sync op
var promise = new Promise((resolve)=>{
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
var channels=[];
snap.forEach(channelChild => {
channels.push(channelChild.key);
});
resolve(channels);
});
});
promise.then((channels)=>{
console.log(channels);
});
If you want a delay on the whole operation:
var promise = new Promise((resolve)=>{
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
var channels=[];
setTimeout(()=>{
snap.forEach(channelChild => {
channels.push(channelChild.key);
});
resolve(channels);
},1000);
});
});
To simulate nested async operations you would add the promises to an array like you were but resolve Promises.all() in the outer promise and resolve your data in the inner promises:
var promise = new Promise((resolve)=>{
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
var snapPromises=[];
snap.forEach(channelChild => {
snapPromises.push(new Promise((snapresolve)=>{
setTimeout(()=>{
snapresolve(channelChild.key);
},1000)
});
});
resolve(Promise.all(snapPromises));
});
});
Related
I'm attempting to define a function that returns a promise. The promise should resolve when a given array is set (push()).
To do this I'm attempting to use a Proxy object (influenced by this):
let a = []
;(async function(){
const observe = array => new Promise(resolve =>
new Proxy(array, {
set(array, key, val) {
array[key] = val;
resolve();
}
}));
while(true){
await observe(a);
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:",`${a.pop()}`);
}
})(a);
;(async function(){
await new Promise(resolve => timerID = setTimeout(resolve, 2000))
a.push('ʕ·͡ᴥ·ʔ');
a.push('¯\(°_o)/¯ ')
})(a)
I can't see why this doesn't work. Does anyone have any idea?
More generally, what is a good way to have a promise resolve on push to an array?
The problems with your attempt:
you invoke .push on the original array, not the proxied one. Where you create the proxy, it is returned to no-one: any reference to it is lost (and will be garbage collected).
The code following after the line with await will execute asynchronously, so after all of your push calls have already executed. That means that console.log will execute when the array already has two elements. Promises are thus not the right tool for what you want, as the resolution of a promise can only be acted upon when all other synchronous code has run to completion. To get notifications during the execution synchronously, you need a synchronous solution, while promises are based on asynchronous execution.
Just to complete the answer, I provide here a simple synchronous callback solution:
function observed(array, cb) {
return new Proxy(array, {
set(array, key, val) {
array[key] = val;
if (!isNaN(key)) cb(); // now it is synchronous
return true;
}
});
}
let a = observed([], () =>
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:", `${a.pop()}`)
);
a.push('ʕ·͡ᴥ·ʔ');
a.push('¯\(°_o)/¯ ');
As noted before: promises are not the right tool when you need synchronous code execution.
When each push is executed asynchronously
You can use promises, if you are sure that each push happens in a separate task, where the promise job queue is processed in between every pair of push calls.
For instance, if you make each push call as part of an input event handler, or as the callback for a setTimeout timer, then it is possible:
function observed(array) {
let resolve = () => null; // dummy
let proxy = new Proxy(array, {
set(array, key, val) {
array[key] = val;
if (!isNaN(key)) resolve();
return true;
}
});
proxy.observe = () => new Promise(r => resolve = r);
return proxy;
}
let a = observed([]);
(async () => {
while (true) {
await a.observe();
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:",`${a.pop()}`);
}
})();
setTimeout(() => a.push('ʕ·͡ᴥ·ʔ'), 100);
setTimeout(() => a.push('¯\(°_o)/¯ '), 100);
I need to somehow loop over the work array passed to _start then
for each of the items in the array, I need to somehow call the corresponding function with the same name.
I don't have control over the number of items in work the array or the number of items, I do know that there will always be a corresponding function.
I don't want to call all the functions at the same time, once the first function resolves after 3 seconds, I then want to call the second function, once the second function resolves after 3 seconds I then want to call the third function. Once the third function resolves after another 3 seconds I want to call _done().
In this example each function takes 3 seconds to complete _done wont gete called for 9 seconds.
function _start(data){
// Insert some kinda native magic loop
}
function _one(){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(1);
}, 3000);
})
};
function _two(){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(2);
}, 3000);
})
};
function _done(){
console.log('All done in 9 seconds)
}
(function(){
var work = ['_one', '_two', '_two'];
_start(work);
})();
Given the order is dicated by the array, you can use reduce to aggregate the promises into a chain
const _start = (...actions) => {
return actions.reduce((chain, action) => {
const func = this[action];
return chain.then(() => func());
}, Promise.resolve());
}
...
_start('_one', '_two', '_three').then(() => console.log('All done'));
See it in action - the example appends an extra then to the chain just to output any results from the promises (probably outwith the scope of this question but something you may have to consider if getting data back is required).
Update
Can see you intend on invoking _start from a different context in which the functions are declared, this is fine but you need to make sure you set the correct context before hand i.e.
const self = this;
(function() {
_start.bind(self)('_one', '_two', '_two');
})();
A function which creates a promise which sleeps:
const sleep = n => () => new Promise(resolve => setTimeout(resolve, n));
A function which sleeps after some input promise:
const sleepAfter = n => p => p.then(sleep(n));
A function which chains a bunch of promises, represented by functions:
const chain = (...promises) => promises.reduce((ret, promise) => ret.then(promise),
Promise.resolve());
Run a bunch of functions yielding promises, sleeping in between:
const _start = promises => chain(promises.map(sleepAfter(3000)));
Now just:
_start(_one, _two, _three).then(_done);
Try using this:
_one().then((firstResponse) {
return_two();
}) .then((secondResponse) => {
*second and first respone are already done*
});
Use promises then
_one().then((responseOne) => {
return _two();
}).then((responseTwo) => {
// _one & _two are done
});
I have variable count of data in an array. I want something, to do this:
function tryThis(num: number): Promise<any> {
return new Promise ((resolve, reject) => {
if(num == 3){
resolve()
}else{
reject();
}
}
}
let arrayOfSomething : Array<number> = [1,2,3,4];
let chain;
// create the chain
arrayOfSomething.forEach( (element) => {
// add element to the chain
chain.catch(() => {
tryThis(element);
});
});
// run chain
chain
.then(() => {
// one of elemnts of the array was 3
})
.catch(() => {
// no "3" found in array
})
So, my goal is to create a Promise chain form an array with variable count of data and at the end catch if all of tryThis() function gives reject. If one of tryThis() function gives resolve in the chain, then jump to the end and exit with resolve.
I know, that my code is not correct, this is just to show what I want to do.
Can anyone help me, to do this?
Thanks!
Maybee you could use Promise.any from bluebird, or similar functionality from some other library: https://www.npmjs.com/package/promise.any or https://www.npmjs.com/package/promise-any.
Promise.any is rejected if all elements are rejected. So, from array of numbers you need to create array of promises, and then call Promise.any. So for example:
let arrayOfSomething = [1,2,3,4];
Promise.any(arrayOfSomething.map(x => tryThis(x))
.then(() => {
// one of elemnts of the array was 3
})
.catch(() => {
// no "3" found in array
})
I have this little program that calculates totals by multiplying a rate and some hours.
The problem I am having is that the function getTasks() always return an empty map.
When I log the fields being entered in the map, they are not empty but they are entered after the function returns the map.
So I am a bit confused why this is happening.
function getTask(taskId) {
return rp({
uri: `${url}/tasks/${taskId}`,
auth: {
user: 'user',
password,
sendImmediately: false
},
json: true
}).then((task) => {
return task;
});
}
function getTasks(entries) {
const taskIds = [];
entries.forEach((entry) => {
const taskId = entry.day_entry.task_id;
if (!taskIds.includes(taskId)) {
taskIds.push(taskId);
}
});
const taskRateMap = new Map();
taskIds.forEach((taskId) => {
return Promise.resolve(getTask(taskId)).then((res) => {
if (res.task.id === taskId) {
taskRateMap.set(taskId, res.task.default_hourly_rate);
console.log(taskRateMap);
}
});
});
return taskRateMap;
}
function calculateTasksTotals(id) {
return co(function* calculateTotalsGen() {
let total = 0;
const entries = yield getEntriesForProject(id);
const tasks = getTasks(entries);
entries.forEach((entry) => {
const rate = tasks.get(entry.day_entry.task_id);
total += rate * entry.day_entry.hours;
});
return total;
});
}
calculateTasksTotals(id)
There are multiple problems with your code:
First off, as soon as you have any asynchronous operation involved in a function, the result of that function is going to be asynchronous. You simply cannot return it synchronously. The async operation finishes sometime later. The function itself returns BEFORE the async operation finishes.
So, you return a promise from any function that uses an async operation and the caller uses that promise to know when things are done or to get the final result.
Your function getTask() is fine. It returns a promise. The .then() inside that function is redundant and not needed since task appears to already be the resolved value of the promise.
Your function getTasks() is trying to synchronously return taskRateMap, but as you've seen in testing, none of the promises have resolved yet so there are no values in taskRateMap yet. In my code version, I use Promise.all() internally to know when all the getTask() operations are done and I return a promise who's resolved value is the taskRateMap object.
The caller of getTasks() can then use that promise and a .then() handler to get access to the taskRateMap object.
Here's one way to implement getTasks():
function getTasks(entries) {
// get all unique task_id values from the entries array
const taskIds = Array.from(new Set(entries.map(entry => entry.day_entry.task_id)));
const taskRateMap = new Map();
// use Promise.all() to know when the whole array of promises is done
// use tasksIds.map() to build an array of promises
return Promise.all(taskIds.map(taskId => {
// make this promise be the return value inside the .map() callback
// so we will end up with an array of promises that will be passed to
// Promise.all()
return getTask(taskId).then(res => {
if (res.task.id === taskId) {
taskRateMap.set(taskId, res.task.default_hourly_rate);
}
})
})).then(() => {
// make final resolved value be the taskRateMap
return taskRateMap;
});
}
getTasks(entries).then(taskRateMap => {
// use taskRateMap here
});
You have an issue with your Promises. Try using Promise.all() providing all the promises as input, and return your map only when all the promises have been resolved.
Otherwise directly return your Promise.all() and create the map in the calling method.
So something like:
const tasksPromises = [];
taskIds.forEach((taskId) => {
tasksPromises.push(getTask(taskId));
});
return Promise.all(tasksPromises);
Then inside your calling method resolve the promises through then and you will have as parameter of the callback function an array where each element is the returned value of the corresponding promise.
I believe this happening because the taskRateMap isn't being populated before it's being returned. You might want to look into Promise.all()
and consider wrapping
promises = taskIds.map((taskId) => {
return Promise.resolve(getTask(taskId)).then((res) => {
if (res.task.id === taskId) {
return [taskId, res.task.default_hourly_rate];
console.log(taskRateMap);
}
});
return Promise.all(promises).then(v => /* taskRateMap*/)
I'm trying to write a function that doesn't return it's value until a Promise inside the function has resolved. Here's a simplified example of what I'm trying to do.
'use strict';
function get(db, id) {
let p = db.command('GET', 'group:${id}');
p.then(g => {
g.memberNames = [];
g.members.forEach(id => {
User.get(db, id)
.then(profile => g.memberNames.push(profile.name))
.catch(err => reject(err));
});
return g;
});
}
It's a function that requests a group id and returns the data for that group. Along the way it's also throwing in the user's names into the data structure to display their names instead of their user id. My issue is that this is running asynchronously and will skip over the .then callbacks. By the time it gets to return g none of the callbacks have been called and g.memberNames is still empty. Is there a way to make the function wait to return g until all callbacks have been called?
I've seen a lot of stuff about await. Is this necessary here? It's highly undesired to add other libraries to my project.
Since your operation to return all the profile names is also async you should return a Promise fulfilled when all the other async operations complete (or reject when one of them is rejected) done with Promise.all
function get(db, id) {
let p = db.command('GET', 'group:${id}');
return p.then(g => {
return Promise.all(g.members.map(id => {
// NOTE: id is shadowing the outer function id parameter
return User.get(db, id).then(profile => profile.name)
})
})
}
Is there a way to make the function wait to return g until all callbacks have been called?
Yes, there is. But you should change your way of thinking from "wait until all the callbacks have been called" to "wait until all the promises are fulfilled". And indeed, the Promise.all function trivially does that - it takes an array of promises and returns a new promise that resolves with an array of the results.
In your case, it would be
function get(db, id) {
return db.command('GET', 'group:${id}').then(g => {
var promises = g.members.map(id => {
// ^^^ never use `forEach`
return User.get(db, id).then(profile => profile.name);
// ^^^^^^ a promise for the name of that profile
});
//
return Promise.all(promises).then(names => {
// ^^^^^^^^^^ fulfills with an array of the names
g.memberNames = names;
return g;
});
});
}