Object literal (hash) with Promise.all - javascript

I have a situation where it would be quite convenient to use Promise.all like so Promise.all({}) instead of the more standard Promise.all([]).
but this doesn't seem to work
Promise.all({a:1,b:2}).then(function(val){
console.log('val:',val);
});
whilst this does of course
Promise.all([1,2,3]).then(function(val){
console.log('val:',val);
});
(what I would expect would be for Promise.all to map the values of the Object literal, but leave the keys intact.)
But the MDN docs for Promise seem to indicate that Promise all will work for any iterable. To my knowledge, an object literal {} is an iterable. So what am I missing?

Here is another async / await ES6 solution:
async function allOf(hash = {}) {
const promises = Object.keys(hash).map(async key => ({[key]: await hash[key]}));
const resolved = await Promise.all(promises);
return resolved.reduce((hash, part) => ({...hash, ...part}), {});
}
This converts the keys into a promise that produces a single element hash. Then at the end we combine all the hashes in the array to a single hash. You could compact this to a one-liner even, at the cost of readability.
async function allOfOneLiner(hash = {}) {
return (await Promise.all(Object.keys(hash).map(async k => ({[k]: await hash[k]})))).reduce((h, p) => ({...h, ...p}), {});
}

Object does not have an Iterator symbol if you look at the mdn documentation for those.
What you can do, is use a tool function to create an object iterable and later consume it.
reference to objectEntries source, however nodejs does not implement Reflect, so for the purpose of using it with node I just change it into using Object.keys()
function objectEntries(obj) {
let index = 0;
// In ES6, you can use strings or symbols as property keys,
// Reflect.ownKeys() retrieves both
let propKeys = Object.keys(obj);
return {
[Symbol.iterator]() {
return this;
},
next() {
if (index < propKeys.length) {
let key = propKeys[index];
index++;
return { value: [key, obj[key]] };
} else {
return { done: true };
}
}
};
}

Use Object.values. Works in Firefox Nightly:
Promise.all(Object.values({a:1,b:2}))
.then(vals => console.log('vals: ' + vals)) // vals: 1,2
.catch(e => console.log(e));
var console = { log: msg => div.innerHTML += msg + "<br>" };
<div id="div"></div>
Then, to put the results back in an object, we can make a Promise.allParams function:
Promise.allParams = o =>
Promise.all(Object.values(o)).then(promises =>
Object.keys(o).reduce((o2, key, i) => (o2[key] = promises[i], o2), {}));
// Demo:
Promise.allParams({a:1,b:2}).then(function(val){
console.log('val: ' + JSON.stringify(val)); // val: {"a":1,"b":2}
});
var console = { log: msg => div.innerHTML += msg + "<br>" };
<div id="div"></div>

Syntax
Promise.all(iterable);
Parameters
iterable
An iterable object, such as an Array. See iterable.

This function does the trick:
Promise.allAssoc = function(object){
var values = [], keys = [];
for(var key in object){
values.push(object[key]);
keys.push(key);
}
return Promise.all(values).then(function(results){
var out = {};
for(var i=0; i<results.length; i++) out[keys[i]] = results[i];
return out;
});
};

Not all objects are iterable by default. You can make an object iterable by defining a ##iterator method. ##iterator is a Well-Known Symbol available as Symbol.iterator:
Specification Name
##iterator
[[Description]]
"Symbol.iterator"
Value and Purpose
A method that returns the default Iterator for an object. Called by the semantics of the for-of statement.
For example, this will make all object iterable (probably not a good idea):
Object.prototype[Symbol.iterator] = function*() {
for(let key of Object.keys(this))
yield this[key];
};
Then you will be able to use
Promise.all({a:1,b:2}).then(function(val){
console.log('val:', val); // [ 1, 2 ]
});

With Babel/ES2015 you can use Object.keys and map to get the values like this:
const obj = {a:1,b:2};
const vals = Object.keys(obj).map(k=>obj[k]);
Promise.all(vals).then( vals => { console.log('vals', vals) });

ES6 way
Promise.hashProperties = async function(object) {
const keys = [];
const values = [];
for (const key in object) {
keys.push(key);
values.push(object[key]);
}
const results = await Promise.all(values);
for (var i=0; i<results.length; i++)
object[keys[i]] = results[i];
return object;
};

Related

Firestore - Clever approach to converting all Firestore.Timestamp objects into Javascript date

I have a collection of objects that I want to retrieve. The objects have some date key:value pairs in them and I want to return all of those as a proper Javascript date. I don't want to declare them all one-by-one, as there are some dates that only exist on some objects, some I might not know about and in general, it's frustrating to declare everything one-by-one.
Here is my code that does not work, how could I get it working?
async function getChargesFromDatabase() {
const chargesCol = fsExpenses.collection('charges');
const chargesDocs = (await chargesCol.limit(50).orderBy('startTs', 'desc').get()).docs.map((doc) => {
const returnDoc: any = {};
for (const [key, value] of Object.entries(Object.entries(doc.data()))) {
returnDoc[key] = value?.toDate() ?? value;
}
return returnDoc;
});
return chargesDocs;
}
You will have to check all the keys as you are doing now by checking if a field is instance of Firestore Timestamp. Try using the following function:
const convertTimestamps = (obj: any) => {
if (obj instanceof firebase.firestore.Timestamp) {
return obj.toDate();
} else if (obj instanceof Object) {
// Check for arrays if needed
Object.keys(obj).forEach((key) => {
obj[key] = convertTimestamps(obj[key]);
});
}
return obj;
};
async function getChargesFromDatabase() {
const chargesCol = fsExpenses.collection('charges');
const chargesSnap = await chargesCol.limit(50).orderBy('startTs', 'desc').get()
const chargesDocs = chargesSnap.docs.map((doc) => convertTimestamps(doc.data()))
}

Loop through arguments passed to a method

I am trying to loop through an argument that is passed to a method and I am getting a TypeError: individualExpenses.map is not a function. What am I doing wrong here?
class ExpenseTracker {
constructor(payCheck, monthlyExpenses) {
this.payCheck = payCheck;
this.monthlyExpenses = monthlyExpenses;
}
storeExpenses(individualExpenses) {
let expenseStore = [];
individualExpenses.map(expense => {
expenseStore.push(expense)
})
console.log(expenseStore)
}
}
const v = new ExpenseTracker({}, {});
v.storeExpenses(1)
You are passing a numerical value to storeExpenses function and applying map over it. map works only on arrays. If you do
v.storeExpenses([1]);
it'll work just fine.
Alternatively, you can build logic to convert a non-array type to an array and use it in your storeExpenses function. This way you can do either of v.storeExpenses(1) or v.storeExpenses([1]) and the function will still work.
e.g.
const wrapToArray = (obj) => {
if (!obj) return [];
return Array.isArray(obj) ? obj : [obj];
};
and then modify your storeExpenses method as below -
storeExpenses(individualExpenses) {
let expenseStore = [];
wrapToArray(individualExpenses).map(expense => {
expenseStore.push(expense)
})
console.log(expenseStore)
}

Array reduce Unexpected use of comma operator no-sequences

I am getting an "Unexpected use of comma operator no-sequences" warning -- on the .reduce - but I am not sure how to resolve this.
const getQueryParams = () =>
this.props.location.search
.replace('?', '')
.split('&')
.reduce((r,e) => (r[e.split('=')[0]] = decodeURIComponent(e.split('=')[1]), r), {});
The code quoted uses (some would say abuses) the comma operator in order to avoid using the function body form of an arrow function. The minimal change to remove the comma operator is to put {} around the function body and do an explicit return:
const getQueryParams = () =>
this.props.location.search
.replace('?', '')
.split('&')
.reduce((r,e) => {
r[e.split('=')[0]] = decodeURIComponent(e.split('=')[1]);
return r;
}, {});
As a matter of style, though, I'd suggest not using reduce there at all. (I have a fair bit of company disliking reduce outside of Functional Programming with predefined, reusable reducers.)
In that code, the reduce is just a loop; the accumulator never changes, it's always the same object. So I'd just use a loop:
const getQueryParams = () => {
const result = {};
for (const e of this.props.location.search.replace("?", "").split("&")) {
result[e.split("=")[0]] = decodeURIComponent(e.split("=")[1]);
}
return result;
};
I'd probably also remove the redundant call to split:
const getQueryParams = () => {
const result = {};
for (const e of this.props.location.search.replace("?", "").split("&")) {
const [key, value] = e.split("=");
result[key] = decodeURIComponent(value);
}
return result;
};
Finally, both keys and values in query strings are URI-encoded, so decodeURIComponent should be used on both:
const getQueryParams = () => {
const result = {};
for (const e of this.props.location.search.replace("?", "").split("&")) {
const [key, value] = e.split("=");
result[decodeURIComponent(key)] = decodeURIComponent(value);
}
return result;
};
It'll work without if the keys are just alphanumerics and such, but it's not correct.
Stepping back from the syntax, though, you don't need to invent your own function for parsing query string parameters. Browsers already have one:
const getQueryParams = () => Object.fromEntries(
new URLSearchParams(this.props.location.search)
.entries()
);
Live Example:
const search = "?bar=Testing%201%202%203&baz=2";
console.log(
Object.fromEntries(
new URLSearchParams(search)
.entries()
)
);
You can rewrite the reduce call, so to avoid an assignment expression (and comma operator), turning the arrow function expression syntax into block syntax (see arrow function expression):
.reduce((r,e) => {
r[e.split('=')[0]] = decodeURIComponent(e.split('=')[1]);
return r;
}, {});
Another approach would be to use Object.assign:
let search = ["item=test","name=code%28","foo=%20bar"]
let result = search.reduce((r,e) =>
Object.assign(r,{[e.split('=')[0]] : decodeURIComponent(e.split('=')[1])}), {});
console.log(result)

Using map() on an iterator

Say we have a Map: let m = new Map();, using m.values() returns a map iterator.
But I can't use forEach() or map() on that iterator and implementing a while loop on that iterator seems like an anti-pattern since ES6 offer functions like map().
So is there a way to use map() on an iterator?
The simplest and least performant way to do this is:
Array.from(m).map(([key,value]) => /* whatever */)
Better yet
Array.from(m, ([key, value]) => /* whatever */))
Array.from takes any iterable or array-like thing and converts it into an array! As Daniel points out in the comments, we can add a mapping function to the conversion to remove an iteration and subsequently an intermediate array.
Using Array.from will move your performance from O(1) to O(n) as #hraban points out in the comments. Since m is a Map, and they can't be infinite, we don't have to worry about an infinite sequence. For most instances, this will suffice.
There are a couple of other ways to loop through a map.
Using forEach
m.forEach((value,key) => /* stuff */ )
Using for..of
var myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');
for (var [key, value] of myMap) {
console.log(key + ' = ' + value);
}
// 0 = zero
// 1 = one
You could define another iterator function to loop over this:
function* generator() {
for (let i = 0; i < 10; i++) {
console.log(i);
yield i;
}
}
function* mapIterator(iterator, mapping) {
for (let i of iterator) {
yield mapping(i);
}
}
let values = generator();
let mapped = mapIterator(values, (i) => {
let result = i*2;
console.log(`x2 = ${result}`);
return result;
});
console.log('The values will be generated right now.');
console.log(Array.from(mapped).join(','));
Now you might ask: why not just use Array.from instead? Because this will run through the entire iterator, save it to a (temporary) array, iterate it again and then do the mapping. If the list is huge (or even potentially infinite) this will lead to unnecessary memory usage.
Of course, if the list of items is fairly small, using Array.from should be more than sufficient.
Other answers here are... Weird. They seem to be re-implementing parts of the iteration protocol. You can just do this:
function* mapIter(iterable, callback) {
for (let x of iterable) {
yield callback(x);
}
}
and if you want a concrete result just use the spread operator ....
[...mapIter([1, 2, 3], x => x**2)]
This simplest and most performant way is to use the second argument to Array.from to achieve this:
const map = new Map()
map.set('a', 1)
map.set('b', 2)
Array.from(map, ([key, value]) => `${key}:${value}`)
// ['a:1', 'b:2']
This approach works for any non-infinite iterable. And it avoids having to use a separate call to Array.from(map).map(...) which would iterate through the iterable twice and be worse for performance.
There is a proposal, that brings multiple helper functions to Iterator: https://github.com/tc39/proposal-iterator-helpers (rendered)
You can use it today by utilizing core-js-pure:
import { from as iterFrom } from "core-js-pure/features/iterator";
// or if it's working for you (it should work according to the docs,
// but hasn't for me for some reason):
// import iterFrom from "core-js-pure/features/iterator/from";
let m = new Map();
m.set("13", 37);
m.set("42", 42);
const arr = iterFrom(m.values())
.map((val) => val * 2)
.toArray();
// prints "[74, 84]"
console.log(arr);
You could retrieve an iterator over the iterable, then return another iterator that calls the mapping callback function on each iterated element.
const map = (iterable, callback) => {
return {
[Symbol.iterator]() {
const iterator = iterable[Symbol.iterator]();
return {
next() {
const r = iterator.next();
if (r.done)
return r;
else {
return {
value: callback(r.value),
done: false,
};
}
}
}
}
}
};
// Arrays are iterable
console.log(...map([0, 1, 2, 3, 4], (num) => 2 * num)); // 0 2 4 6 8
Take a look at https://www.npmjs.com/package/fluent-iterable
Works with all of iterables (Map, generator function, array) and async iterables.
const map = new Map();
...
console.log(fluent(map).filter(..).map(..));
You could use itiriri that implements array-like methods for iterables:
import { query } from 'itiriri';
let m = new Map();
// set map ...
query(m).filter([k, v] => k < 10).forEach([k, v] => console.log(v));
let arr = query(m.values()).map(v => v * 10).toArray();
In case someone needs the typescript version:
function* mapIter<T1, T2>(iterable: IterableIterator<T1>, callback: (value: T1) => T2) {
for (let x of iterable) {
yield callback(x);
}
}
Based on the answer from MartyO256 (https://stackoverflow.com/a/53159921/7895659), a refactored typescript approach could be the following one:
function mapIterator<TIn, TOut>(
iterator: Iterator<TIn>,
callback: (input: TIn) => TOut,
): Iterator<TOut> {
return {
next() {
const result: IteratorResult<TIn> = iterator.next();
if (result.done === true) {
return result;
} else {
return {
done: false,
value: callback(result.value),
};
}
},
};
}
export function mapIterable<TIn, TOut>(
iterable: Iterable<TIn>,
callback: (input: TIn) => TOut,
): Iterable<TOut> {
const iterator: Iterator<TIn> = iterable[Symbol.iterator]();
const mappedIterator: Iterator<TOut> = mapIterator(iterator, callback);
return {
[Symbol.iterator]: () => mappedIterator,
};
}

JavaScript: Creating a function to reject array elements found to be truthy, trouble with values in objects in the array

I'm working on a code challenge assignment. Create a function, reject, that takes an array and a callback function, and removes from the array any items that are found truthy when the callback function is run against them. I've written the following:
function reject(collection, callback) {
for (var i = 0; i < collection.length; i++) {
if(callback(collection[i]) === true){
collection.splice(i, 1);
}
}
return collection;
}
and where I'm hitting a wall is a test with an array of key-value pairs. The failing test:
var obj = {a:1, b:2, c:3, d:4};
var isOdd = function(value, key, collection) { return value % 2 !== 0; };
var evens = reject(obj, isOdd);
expect(evens).to.eql({b:2, d:4});
Lack of experience has exhausted my ability to search for answers effectively, so here we are. Any help or guidance is appreciated.
Edited to add:
Misread the tests in the original instructions (then failed to catch it when copy/pasting the test). I definitely know the difference between an object and an array, just thought I saw [{a:1, b:2, c:3, d:4}] in the document but it was actually ({a:1, b:2, c:3, d:4}) for whatever reason. Sorry.
I think this is what you're trying to do?
function reject(collection, callback) {
Object.keys(collection).forEach(function(key){
if(callback(collection[key], key, collection)){
delete collection[key];
}
});
return collection;
}
var obj = {a:1, b:2, c:3, d:4};
var isOdd = function(value, key, collection) { return value % 2 !== 0; };
var evens = reject(obj, isOdd);
console.log(evens); //prints { b: 2, d: 4 }
You have the right idea, however you need to look at the difference between a JavaScript object and array. Read this and learn the difference.
A JavaScript object does not have the property of .length to return the size of the collection. Use the following loop instead:
for (var key in collection)
A collection does not have the property .splice, that is for arrays. Instead of using .splice to remove the item use
delete collection[key]
Finally, pass the item in the collection to the callback
callback(collection[key])
Updated Answer:
function reject(collection, callback) {
for (var key in collection) {
if(callback(collection[key]) === true) {
delete collection[key];
}
}
return collection;
}
var obj = {a:1, b:2, c:3, d:4}; // Use for an object passed
var obj2 = [1, 2, 3, 4]; // Use as an array passed
var isOdd = function(value) { return value % 2 !== 0; };
var evens = reject(obj, isOdd);
console.log(evens);
// expect(evens).to.eql({b:2, d:4});
Here's the solution I ended up with. To clarify, the tests were passing in arrays and objects, so that's why I first had trouble (with the objects) and then there was some confusion in in the answers. I wrote:
function reject(collection, callback) {
if(Array.isArray(collection)){
for (var i = 0; i < collection.length; i++) {
if(callback(collection[i]) === true){
collection.splice(i, 1);
}
}
} else {
for (number in collection){
if(callback(collection[number]) === true){
delete collection[number];
}
}
};
return collection;
}
I know it could likely be much cleaner, but just for the sake of clarity, I wanted to show a solution that works.
I have created a not() function, which accepts a function and can be passed to a filter function:
// wraps passed function, so that when called,
// the return value will be negated
function not(a){
return function(){
return !a.apply(a, arguments);
}
}
Usage:
// some sample data
var animals = [
{name: 'Pete', type: 'fish'},
{name: 'Jim', type: 'fish'},
{name: 'Tom', type: 'cat'}
];
// a simple filter callback
var isCat = function(animal){
return animal.type === 'cat';
};
// gather all animals, which are not cats
var fishes = animals.filter(not(isCat));
I know it could likely be much cleaner
Just for the fun, here is how I alter the answer step by step to make it a bit cleaner.
Making it immutable
Changing the collection will change the original object, which is passed to the function,
since arrays and objects are reference types. To solve this, you can clone the collection and work
on that, or you can copy the elements, which are not rejected by the callback. I'm doing the latter.
function reject(collection, callback) {
var ret;
if(Array.isArray(collection)){
ret = [];
for (var i = 0; i < collection.length; i++) {
if(!callback(collection[i])){
ret.push(collection[i]);
}
}
} else {
ret = {};
for (number in collection){
if(!callback(collection[number])){
ret[number] = collection[number];
}
}
}
return ret;
}
Shortening with ES5
The loops' mechanics and the actual code done by the loop is entangled, we can have a much cleaner
code, if we stop concentrating on how to write a loop and let JS do it. For example: notice how
I refer to the individual elements of the array collection as value, instead of collection[i].
function reject(collection, callback) {
var ret;
if(Array.isArray(collection)){
ret = [];
collection.forEach(function(value){
if(!callback(value)){
ret.push(value);
}
});
} else {
ret = {};
Object.keys(collection).forEach(function(key){
var value = collection[key];
if(!callback(value)){
ret[key] = value;
}
});
}
return ret;
}
Changing if to filter()
Array.prototype.filter() is a bit more useful for us, than forEach, since in the core of the loop you can
simply return a truthy or falsy value and filter will handle collecting the data to a new array based on
that automatically for you.
function reject(collection, callback) {
var ret;
if(Array.isArray(collection)){
ret = collection.filter(function(value){
return !callback(value);
});
} else {
ret = {};
Object.keys(collection).filter(function(key){
return !callback(collection[key]);
}).forEach(function(key){
ret[key] = collection[key];
});
}
return ret;
}
Using reduce for objects
The goal would be to minimize functions, which go outside from their scope in order to work correctly.
In the objects part we can use Array.prototype.reduce() instead of forEach() and simply return it's
output directly to the ret value, when we are done, just as we did in the Array part with filter().
function reject(collection, callback) {
var ret;
if(Array.isArray(collection)){
ret = collection.filter(function(value){
return !callback(value);
});
} else {
ret = Object.keys(collection).filter(function(key){
return !callback(collection[key]);
}).reduce(function(obj, key){
obj[key] = collection[key];
return obj;
}, {});
}
return ret;
}
Shortening functions with ES6
Since we are already using Array.isArray(), which is an ES6 method, we can try using arrow functions
to compress anonymus functions.
function reject(collection, callback) {
var ret;
if(Array.isArray(collection)){
ret = collection.filter(value => !callback(value));
} else {
ret = Object.keys(collection)
.filter(key => !callback(collection[key]))
.reduce((obj, key) => {
obj[key] = collection[key];
return obj;
}, {})
;
}
return ret;
}
We don't need the ret variable
We previously removed the need to access the ret value in our logics, we can use a ternary operator
to directly return the value generated by the expressions.
function reject(collection, callback) {
return (
Array.isArray(collection)
? collection
.filter(value => !callback(value))
: Object.keys(collection)
.filter(key => !callback(collection[key]))
.reduce((obj, key) => {
obj[key] = collection[key];
return obj;
}, {})
)
}

Categories

Resources