I am setting the below function in order to retrieve it after.
However, for some reason, it does not work:
constructor(private nativeStorage: NativeStorage) {
// I am calling it this way:
this.userId = 123;
this.getItem("userId", this.userId);
// If I replace the shortcut by the below it works fine
/* this.nativeStorage.getItem("userId).then(
data => this.userId = data,
error => console.error(error)
); */
}
getItem(itemKey, itemValue) {
return this.nativeStorage.getItem(itemKey).then(
data => itemValue = data,
error => console.error(error)
);
}
I believe that I am missing something here, that's why it doesn't work
You're assigning data to itemValue which is just a copy of this.userId. JS doesn't support pass by reference that could make that possible. Instead you can just use itemKey to assign to the class instance directly like this:
getItem(itemKey) {
return this.nativeStorage.getItem(itemKey).then(
data => this[itemKey] = data, // assign data to this[itemKey] which will be this.userId (in the example above)
error => console.error(error)
);
}
JavaScript is not supporting call by reference. It's only by value arg passing. So values can be updated only by object references only.
In reference to the part/comments where you can't have an async constructor and therefore I extract all the async parts into a factory.
I have no experience yet with ionic, so you should take the following as pseudocode:
import { NativeStorage } from '#ionic-native/native-storage';
function constructor(public userId: String){ }
//and the factory function
//don't know how exactly how to export stuff in that framework
function createById(userId){
return NativeStorage.getItem('userId')
.then(
userId => new YourClass(userId)
error => console.error(error)
)
}
or in the case where you want to assign multiple properties:
//a utility to resolve with { key: Promise<value> } mappings + pretty much everything you throw at it.
//warning: doesn't resolve promises in nested structures (by design),
//stuff like {key: { foo: Promise<value> }}
//ideally you'd put that in a seperate module since it can be handy in many places.
function resolve(obj){
if(Array.isArray(obj)
return Promise.all(obj);
if(typeof obj === "function")
return Promise.resolve().then(obj);
if(!obj || typeof obj !== "object" || "then" in obj && typeof obj.then === "function")
return Promise.resolve(obj);
var keys = Object.keys(obj);
return Promise.all( keys.map(k => obj[k]) )
.then(values => combine(keys, values));
}
//takes two Arrays, a matching set of keys and values and combines that into an object.
function combine(keys, values){
return keys.reduce((acc, key, index) => {
acc[key] = values[index];
return acc;
}, {});
}
const assignTo = target => source => Object.assign(target, source);
//in this case we use assign all async values outside of the constructor
function constructor(){
this.userId = null;
this.userName = null;
}
function createById(userId){
return resolve({
userId: NativeStorage.getItem('userId'),
userName: NativeStorage.getItem('userName'),
//...
}).then(
assignTo( new YourClass() ),
error => console.error(error)
)
}
Or if this is still to much repetition for you:
//a utility to fetch multiple items at once
function getItems(...keys){
return Promise.all( keys.map( key => NativeStorage.getItem(key) ) )
.then(values => combine(keys, values));
}
//and a quick test
getItems("userId", "userName", ...).then(console.log);
//or in the context of the last snippet:
getItems("userId", "userName", ...).then(assignTo( new YourClass() ));
Hope this helps shows you a different approach to your problems.
Related
In my project (VUE + Vuex) I need to make some API requests simultaneously, according to some contents and then process the results.
The getters.api_props(key) function will return the method ('post', 'patch', 'delete') or false if there is no need for a request. It will also return the url and the object that is needed for the request.
The api method returns the request as a Promise using axios.
Here is my code so far:
var contents = {person: {...}, info: {...}}
var promiseArray = [];
for (var key in contents) {
let [method, url, hash] = getters.api_props(key);
if (method) { promiseArray.push(api[method](url, hash)) }
}
await Promise.allSettled(promiseArray).then((results) => {
results.map(r => {
// THE RESULTS WILL BE PROCESSED HERE like:
// commit("save", [key, r])
console.info(r)
})
}).catch(e => console.log('ERROR:::',e)).finally(commit("backup"))
The problem is that the results does not include the 'key' so the save method that is called cannot know where to save the results.
Can you propose a fix or a better solution?
I would recommend to write
const contents = {person: {...}, info: {...}}
cosnt promiseArray = [];
for (const key in contents) {
let [method, url, hash] = getters.api_props(key);
if (method) {
promiseArray.push(api[method](url, hash)).then(value => ({
key,
status: 'fulfilled',
value
}), reason => ({
key,
status: 'rejected',
reason
})))
}
}
const results = await Promise.all(promiseArray);
for (const r of results) {
if (r.status=='fulfilled') {
console.info(r.key, ':', r.value.data)
commit("save", [r.key, r.value]);
} else if (r.status=='rejected') {
console.warn(r.key, ':', r.reason)
}
})
commit("backup");
So, to answer my own question, after Bergi's comments I filled promiseArray with
api[method](url, hash).then((r) => [key, r]).catch((e) => {throw [key, e.response]})
and then found the key that I needed:
await Promise.allSettled(promiseArray).then((results) => {
results.map((r) => {
if (r.status=='fulfilled') {
console.info(r.value[0],':',r.value[1].data)
}
if (r.status=='rejected') {
console.warn(r.reason[0],':',r.reason[1])
}
})
})
You obviously don't need to take this, but I fiddled with it for a while and this is what I liked best:
import forEach from 'lodash/forEach'
import mapValues from 'lodash/mapValues'
import { api, getters } from 'somewhere'
var contents = {person: {...}, info: {...}}
const promiseContents = mapValues(contents, (value, key) => {
let [method, url, hash] = getters.api_props(key);
if (!method) { return }
return api[method](url, hash)
})
await Promise.allSettled(Object.values(promiseContents))
forEach(promiseContents, (promise, key) => {
promise.then(response => {
if (promise.status === 'rejected') {
console.warn(key, ':', response)
}
console.info(key, ':', value.data)
})
})
The big requirement is that you include lodash in the project, but that is not an unusual ask in javascript projects.
mapValues allows you to keep the structure of your contents object while replacing the values with promises. I just use await on Promise.allSettled to tell the rest of the code when it can go. I just ignore the results.
Finally, using lodash's forEach I interpret the results. The advantage here is that every promise is run in a function alongside the key from your original contents object.
I like doing it this way because it doesn't require you to create a [key, result] array. That said, either way works fine.
I'm fetching an API of dogs, it returns an object.
I want to make changes in the object before I convert
it to an array and setState() it.
Problem is it returns 'undefined'.
I assume the function is ok because it does work if I run in outside the fetch, or maybe I'm missing something here?
componentDidMount() {
fetch("https://dog.ceo/api/breeds/list/all")
(res => res.json())
.then(res => res.message)
.then(res => this.processReq(res)) // <--- undefined
.then(res => Object.entries(res))
.then(res => this.setState({ allBreeds: res }));
}
processReq = breed =>
Object.keys(breed).forEach(key => {
if (key === "australian") {
breed["shepherd"] = [key];
delete breed[key];
} else if (key === "germanshepherd") {
breed["shepherd"].push("german");
delete breed[key];
}
});
Is there a way to manipulate the object during the fetch?
Or maybe it would be easier to setState() it as an array and then process it?
Actually forEach function doesn't return anything so that's why it's giving undefined, you should change it to:
processReq = breed => {
Object.keys(breed).forEach(key => {
if (key === "australian") {
breed["shepherd"] = [key];
delete breed[key];
} else if (key === "germanshepherd") {
breed["shepherd"].push("german");
delete breed[key];
}
});
return breed;
}
The processReq function doesn't have a return statement, so it just returns undefined. Just put a return breed; on the end of it and you'll be fine.
I have a list of constants defined like this
const actions = {}
// Home
actions.HOME = {}
actions.HOME.SET_PROFILE_ID = 'SET_PROFILE_ID'
actions.HOME.LOAD_PROFILE = 'HOME_LOAD_PROFILE'
actions.HOME.SET_PROFILE = 'HOME_SET_PROFILE'
actions.OUTSIDE = {}
actions.OUTSIDE.UPDATE_PROFILE_ID = 'SET_PROFILE_ID' // this should error
module.exports = actions
The objects with in objects is to help intelisense so devs can narrow down as they go.
I want to use jest to write a test that will check to make sure no 2 constants have the same value, no matter the depth, otherwise it can create very odd errors that are hard to debug at run time. I don't really understand the documentation and how I can do this. https://jestjs.io/docs/en/using-matchers But this is my first time making any unit tests.
Thank you
-Edit
This is what I have so far. Based on Jared Smiths comments, I am no where close to the right answer as this is too simple. It only finds the first mistake, not all of them.
describe('Actions.js', () => {
it('verify no duplicate action values', () => {
const flattenActions = []
_.forEach(actions, store => {
_.forEach(store, action => {
flattenActions.push(action)
})
})
const testedActions = []
_.forEach(flattenActions, action => {
expect(testedActions).not.toContain(action)
testedActions.push(action)
})
})
})
First of all you can get all the values of your actions
function getDeepValues(obj) {
let values = [];
for (const key in obj) {
if (typeof obj[key] === 'object') {
const subVals = getDeepValues(obj[key]);
values = [...values, ...subVals];
} else {
values.push(obj[key]);
}
}
return values;
}
Will output something like this:
[ 'SET_PROFILE_ID',
'HOME_LOAD_PROFILE',
'HOME_SET_PROFILE',
'SET_PROFILE_ID' ]
And then you test if the array doesn't contain any duplicates:
function arrayHasNoDuplicate(arr) {
return arr.every(num => arr.indexOf(num) === arr.lastIndexOf(num));
};
Now you have to run your tests:
describe('Actions.js', () => {
it('verify no duplicate action values', () => {
const actionsArray = getDeepValues(actions);
const hasNoDuplicates = arrayHasNoDuplicate(actionsArray);
expect(hasNoDuplicates).toBeTruthy();
})
})
Hope it helps!
A unit test of this complexity probably merits its own matcher, which you could define recursively like this:
expect.extend({
toHaveUniqueValues(received) {
const keys = []
const values = new Set()
function assertUniqueValues(object) {
if (typeof object === 'object' && object !== null) {
for (const key in object) {
if (object.hasOwnProperty(key)) {
keys.push(key)
assertUniqueValues(object[key])
keys.pop()
}
}
} else if (values.has(object)) {
throw new Error(`expected ${keys.join('.')} to not have duplicate value ${String(object)}`)
}
values.add(object)
}
try {
assertUniqueValues(received)
return {
message: () => 'expected object to have duplicate values',
pass: true
}
} catch (error) {
return {
message: () => error.message,
pass: false
}
}
}
})
The message that goes with pass: true, as explained in the documentation, is in case the test is negated and the negation of the test fails. Set is preferred to Array for storing the values found so far, because lookup using has() is O(1) time on average while using includes() is O(n) time.
To use the above matcher in your case:
describe('actions', () => {
it('should not have duplicate values', () => {
expect(actions).toHaveUniqueValues()
})
})
In this case it will complete with the error:
expected OUTSIDE.UPDATE_PROFILE_ID to not have duplicate value SET_PROFILE_ID
I have the following function in typescript:
getConfigurations() {
let sessionConfig = sessionStorage.getItem('config');
if(config)
return of(sessionConfig);
else {
this.dataService.getRoute('configurations').subscribe(
route => this.http.get<any[]>(route.ToString()).subscribe(
result => {
sessionStorage.setItem('config', result);
return of(result);
},
error => {
alert(error);
}),
error => {
alert(error);
}
);
}
}
The function should return a string if the sessionStorage key is already existent or use dataService to retrieve the value from back end and then set the value in the session. I'd like to return the value set in sessionStorage too (result), but I couldn't find a way to do it.
The function getRoute from dataService is:
getRoute(service: string){
return this.http.get('urlToMyBackendWebApi');
}
Where http is the Angular HttpClient.
How could I get the value returned from getConfigurations() ?
I tried to subscribe to getConfigurations. I have no problems with the if condition (the string sessionConfig is returned) but how to get the result from the else condition? Should I return an observable of the entire part? In this case, how could I read the return?
Don't subscribe to the observable, return it, and use the tap operator to store the response:
getConfigurations() {
let sessionConfig = sessionStorage.getItem('config');
if(config)
return of(sessionConfig);
else {
return this.dataService.getRoute('configurations').pipe(
mergeMap(route => this.http.get<any[]>(route.ToString()),
tap(result => sessionStorage.setItem('config', result))
);
}
}
While building custom endpoints I often need to resolve a complex object containing promises.
For illustration, take this example:
Given known user's id, employeeId and memberGroupsIds (an array):
var loginResponse = {
userprofile : getProfile(id)
companyInfo : {
company : getCompany(employeeId)
companyRelations : getPriviligedInfo(employeeId)
}
groups : getGroups(memberGroupsIds)
}
This logic works for synchronous functions that just return their values. But with functions that return promises I have to manually push all of them into an array to ensure they are resolved before using the final object.
I find the above code very easy to understand, and I'm looking for a signature that gives some of that, while still ensuring that the promises are resolved before sending a final object to the client.
The problem is not making it work, but making it beautiful and easy to read.
The best answer would ensure that the values are returned to the expected keys in the object and that all the promises are resolved in parallel, while maintaining a structure that is somewhat compatible with that of synchronous functions.
Or, if I'm missing the point and looking at this all wrong, how should I be looking at it?
You could use the helper function below. It takes an object and returns a promise that resolves when all nested promises have been resolved. The returned promise will provide as value the same object, which will have mutated with all its embedded promises replaced by their corresponding values.
function promiseRecursive(obj) {
const getPromises = obj =>
Object.keys(obj).reduce( (acc, key) =>
Object(obj[key]) !== obj[key]
? acc
: acc.concat(
typeof obj[key].then === "function"
? [[obj, key]]
: getPromises(obj[key])
)
, []);
const all = getPromises(obj);
return Promise.all(all.map(([obj, key]) => obj[key])).then( responses =>
(all.forEach( ([obj, key], i) => obj[key] = responses[i]), obj)
);
}
You would call it like this:
var loginResponsePromise = promiseRecursive({
userprofile : getProfile(10),
companyInfo : {
company : getCompany(101),
companyRelations : getPriviligedInfo(101)
},
groups : getGroups([5])
});
function promiseRecursive(obj) {
const getPromises = obj =>
Object.keys(obj).reduce( (acc, key) =>
Object(obj[key]) !== obj[key] ? acc
: acc.concat(typeof obj[key].then === "function" ? [[obj, key]]
: getPromises(obj[key]))
, []);
const all = getPromises(obj);
return Promise.all(all.map(([obj, key]) => obj[key])).then( responses =>
(all.forEach( ([obj, key], i) => obj[key] = responses[i]), obj)
);
}
// Example promise-returning functions
const wait = ms => new Promise( resolve => setTimeout(resolve, ms) ),
getProfile = id => wait(100).then(_ => ({userName: 'user' + id,id})),
getCompany = employeeId => wait(200).then(_ => ({employeeName: 'employee' + employeeId, employeeId})),
getPriviligedInfo = employeeId => wait(500).then(_ => ({privs: 'privInfo' + employeeId, employeeId})),
getGroups = memberGroupsIds => wait(400).then(_ => ({groups: ['group' + memberGroupsIds[0]],memberGroupsIds}));
// Sample input passed to `promiseRecursive` function
const loginResponsePromise = promiseRecursive({
userprofile : getProfile(10),
companyInfo : {
company : getCompany(101),
companyRelations : getPriviligedInfo(101)
},
groups : getGroups([5])
});
// Display the resolved object
loginResponsePromise.then( o => console.log(o) );
.as-console-wrapper { max-height: 100% !important; top: 0; }
I usually solve this kind of scenarios with Bluebird's join http://bluebirdjs.com/docs/api/promise.join.html :
const Promise = require('bluebird');
return Promise.join(
getProfile(id),
getCompany(employeeId),
getPrivilegedInfo(employeeId),
getGroups(memberGroupsIds),
(userProfile, company, companyRelations, groups) => {
return {
userProfile: userProfile,
companyInfo: {
company: company,
companyRelations: companyRelations
},
groups: groups
};
}
);
Using new ES6 features I would write something like this:
Promise.all([
getProfile(id),
getCompany(employeeId),
getPriviligedInfo(employeeId),
getGroups(memberGroupsIds)
])
.then(response => {
const [ userprofile, company, companyRelations, groups ] = response
const loginResponse = {
userprofile,
companyInfo : {
company,
companyRelations
},
groups
}
})
.catch(err => console.error(err))
Maybe the interesting part is that Promise.all() keep the input arguments order not depending on which resolves first. So in next step, using Destructuring Array assignment, the code looks like synchronous.