I really struggle with data initializing and promises. I am using Ionic 3 with Angular and Ionic storage, but my question is mainly related to how promises operate.
Basically I would like to achieve the following:
when my app starts it should use the local storage collection
if the local storage collection does not exist or is empty, create a new one with http
if the http fails, create a collection with local data.
My solution so far:
getNewsItems():Promise<any> {
return this.storage.get(this.newsKey).then((data) => {
if(data == null)
{
return (this.buildNewsItemsViaHttp());
} else {
return (data);
}
});
}
private buildNewsItemsViaHttp(){
return new Promise(resolve => {
this.http.get('some/url/to/fetch/data')
.subscribe(
data => {
this.newsCollection = data;
this.storage.set(this.newsKey, this.newsCollection);
resolve(this.newsCollection);
},
(err) => {
resolve (this.buildNewsItemsViaLocalJSON());
}
);
});
}
private buildNewsItemsViaLocalJSON() {
return new Promise(resolve => {
this.http.get('assets/data/newsCollectionLocal.json')
.subscribe(
data => {
this.newsCollection = data;
this.storage.set(this.newsKey, this.newsCollection);
resolve(this.newsCollection);
},
(err) => {
console.log(err);
}
);
});}
I don't like some parts of it, for example returning a promise inside a promise - is this actually an issue?
Thanks in advance
A clean solution could be using async\await methods:
async buildNewsItemsViaHttp(){
return await this.http.get()....
}
async buildNewsItemsViaLocalJSON(){
return await this.http.get()....
}
async getNewsItems(){
return await this.storage.get()...
}
async getItems(){
return ((await this.getNewsItems()) || (await this.buildNewsItemsViaLocalJSON()) || (await this.buildNewsItemsViaHttp()));
}
usage:
const items = await this.getItems();
You can optimize the resources, cache them and return them in each function.
Example:
async buildNewsItemsViaHttp(){
let result = cache.get(); // todo
if(!result){
result = await this.http.get()...
cache.set(result)
}
return result;
}
Related
I am making my first hybrid web app using react hook.
I am faced with a timing problem with promise and context api.
Here are the logic I am facing with.
A function fetchApplications gets data from firebase firestore, it is defined in globalcontext.js (called in tableView)
tableView.js calls fetchApplications in useEffect.
fetchApplications is defined as promise function, I expect it will resolve(return) data until data fetched, it will resolove(return) object like {code:200, data:data}
problem
in the fetchData.code,
Cannot read property 'code' of undefined
Here is my code
In the tableview.js
React.useEffect(() => {
context.fetchApplications(auth.userId, "")
.then(function (fetchData) {
console.log("Fetched data: ", fetchData); ///this is undefined
if (fetchData.code !== 200) { /// error part
alert(fetchData.msg);
}
if (!fetchData.data) {
alert("No applications");
}
setsData(fetchData.data);
});
}, []);
In the GlobalContext.js
async function fetchApplications(userId, role) {
return new Promise((resolve, reject) => {
// resolve({ code: 200, data: "data" }); //If I add this code, it will be alright.
let dataArray = [];
let applicationRef = db
.collection("Users")
.doc(userId)
.collection("Applications");
applicationRef
.get()
.then(function (qs) {
qs.forEach(function (doc) { //doesn't work
console.log(doc.id, " => ", doc.data());
console.log(doc.size, " => ", typeof doc);
dataArray.push(doc.data());
});
return Promise.resolve(dataArray);
})
.then((data) => {
console.log("Global, Fetched Data", dataArray);
if (data) {
resolve({ code: 200, data: data });
}
return;
});
}).catch(function (error) {
reject({ code: 400, msg: "시스템에러 고객센터에 문의해주세요" });
});
}
wrote in codesendbox
If I was write the wrong way of promise, please let me know.
thanks
You're implementing a couple of bad practices and have some major issues. For starters, fetchApplications is marked as async but you're returning a manually created promise which is quite overkill as your fetching code actually generates a promise which you should directly return. Furthermore:
.then(function (qs) {
qs.forEach(function (doc) { //doesn't work
console.log(doc.id, " => ", doc.data());
console.log(doc.size, " => ", typeof doc);
dataArray.push(doc.data());
});
return Promise.resolve(dataArray);
})
I am not sure what exactly "//doesn't work" should mean but return Promise.resolve(dataArray); won't cut it for you. You're already in the then chain, so you can't resolve anything from the main promise at this point. You should just pass the data to the next then callback as return dataArray;.
All in all, I will suggest ditching the then-ables syntax all together and migrate to async/await altogether:
async function fetchApplications(userId, role) {
try {
const dataArray = [];
const applicationRef = db
.collection("Users")
.doc(userId)
.collection("Applications");
const querySnapshot = await applicationRef.get();
querySnapshot.forEach(doc => {
dataArray.push(doc.data());
});
return {
code: 200,
data: dataArray
};
}
catch (error) {
return {
code: 400,
msg: '시스템에러 고객센터에 문의해주세요'
};
}
}
Then, in your react component/hook:
React.useEffect(() => {
const fetchApplicationDataAsync = async () => {
const result = await context.fetchApplications(auth.userId, "");
if (result.code !== 200) {
alert(result.msg);
}
if (!result.data) {
alert("No applications");
}
setsData(result.data);
}
fetchApplicationDataAsync();
}, [auth.userId]);
Another problem and bad practice is that you're not specifying your dependencies for the useEffect hook. You have 2 external dependencies: the auth.userId paramater and the ontext.fetchApplications function. We alleviate one of the problem by creating the fetch function in the body of useEffect itself. However, the auth.userId should go into the dependency array as stated above.
You have to check for fetchData to be defined before accessing its properties.
A short form would be
if (fetchData && fetchData.code !== 200){...}
Applied to your code:
React.useEffect(() => {
context.fetchApplications(auth.userId, "")
.then(function (fetchData) {
console.log("Fetched data: ", fetchData); ///this is undefined
if (fetchData && fetchData.code !== 200) { /// error part
alert(fetchData.msg);
}else {
alert("No applications");
}
setsData(fetchData.data);
});
}, []);
By calling then() on the fetchApplications() function, as follows, you pass to the callback the fullfilment value from the Promise returned by fetchApplications() (i.e. fetchData gets the value returned by fetchApplications()).
context.fetchApplications(auth.userId, "")
.then(function (fetchData) {...}
However, fetchApplications() returns a Promise that resolves with undefined because, actually, you don't return the Promises chain. This is why you get an error on fetchData.code.
Adapting fetchApplications() as follows (using await, since you use async) should do the trick (untested!):
async function fetchApplications(userId, role) {
try {
let dataArray = [];
let applicationRef = db
.collection('Users')
.doc(userId)
.collection('Applications');
const qs = await applicationRef.get();
qs.forEach(doc => {
console.log(doc.id, ' => ', doc.data());
console.log(doc.size, ' => ', typeof doc);
dataArray.push(doc.data());
});
return { code: 200, data: dataArray };
} catch (error) {
return { code: 400, msg: '시스템에러 고객센터에 문의해주세요' };
}
}
Note that in any case you return an object with a code property, so no more problem when doing fetchData.code.
I know it is the most frquently asked question in javascript regarding asynchronous behaviour of functions. Still I am not able to figure out a working solution for my use case.
What I am trying to do is lookup the redis cache using a key. I am using the exists function to check the key is present or not. If present then i'll return from cache if not then i'll set the key and then make a db call using this key as a db param. It seems very simple but no matter what I do i'm unable to return the value from the cache or the DB. If I make these calls outside the exist function then it works as the resolver function(graphql resolver) is an async function. This resolver functions expects a return value.
So here is the code by which i'm unable to retun the value in any scenario:-
empId: async(obj, params, ctx, resolverInfo) => {
await client.exists(obj.empId, async function(err, reply) {
if (reply == 1) {
return await getAsync(obj.empId).then(res => {
console.log(res);
return res;
})
} else {
return await db.one('SELECT * FROM iuidtest WHERE empid = $1', [obj.empId])
.then(iuidtest => {
console.log(iuidtest.empid);
return iuidtest.empid;
})
}
});
const doSomethingWith = empid => {
console.log("empid = ", empid);
}
I am getting the correct values in console but am unable to return. However if I directly make these calls in my resolver function i.e. outside of the redis exists function I am able to return the value.
empId: async(obj, params, ctx, resolverInfo) => {
return await getAsync(obj.empId).then(res => {
console.log(res);
return res;
This way I am able to return the value from the resolver function. It would be really of great help if anybody can provide the working code for this instead of other links regarding how to return from async function using callbacks and promises. Here is another post reg the same. :-
Redis async library does not have function that are in redis library for node.js
Thanks in advance!
If client.exists returns promise, the same code can be written as below:
empId: async (obj, params, ctx, resolverInfo) => {
const exists = await client.exists(obj.empId);
if (exists === 1) {
return getAsync(obj.empId);
}
return await db.one('SELECT * FROM iuidtest WHERE empid = $1', [obj.empId])
.then(iuidtest => {
return iuidtest.empid;
});
}
If client.exists only accepts callback, then the code can be written as:
empId: async (obj, params, ctx, resolverInfo) => {
async function empIdExists(empId) {
return new Promise(function resolver(resolve, reject) {
client.exists(obj.empId, function(err, reply) {
if (err) {
reject(err);
return;
}
if (reply == 1) {
resolve(1);
return;
} else {
resolve(0);
return;
}
})
});
}
const exists = await empIdExists(obj.empId);
if (exists === 1) {
return getAsync(obj.empId);
}
return await db.one('SELECT * FROM iuidtest WHERE empid = $1', [obj.empId])
.then(iuidtest => {
return iuidtest.empid;
});
}
In the second version, notice that I have wrapped the client.exists call into an async function & called using await keyword.
In node.js i have a databaseMapper.js file, that uses the Ojai node MapR api. to extract data. So far i have it working with single documents, but since this is an async api, i have a bit of issues with querying multiple documents.
This is what i have so far:
function queryResultPromise(queryResult) {
//this should handle multiple promises
return new Promise((resolve, reject) => {
queryResult.on("data", resolve);
// ...presumably something here to hook an error event and call `reject`...
});
}
const getAllWithCondition = async (connectionString, tablename, condition) =>{
const connection = await ConnectionManager.getConnection(connectionString);
try {
const newStore = await connection.getStore(tablename);
const queryResult = await newStore.find(condition);
return await queryResultPromise(queryResult);
} finally {
connection.close();
}
}
here it will only return the first because queryResultPromise will resolve on the first document.. however the callback with "data" may occur multiple times, before the queryResult will end like this queryResult.on('end', () => connection.close())
i tried using something like Promise.all() to resolve all of them, but I'm not sure how i include the queryResult.on callback into this logic
This will work
const queryResultPromise = (queryResult) => {
return new Promise((resolve, reject) => {
let result = [];
queryResult.on('data', (data) => {
result.push(data)
});
queryResult.on('end', (data) => {
resolve(result);
});
queryResult.on('error', (err) => {
reject(err);
})
});
};
The function below calls several asynchronous functions in a for loop. It's parsing different CSV files to build a single JavaScript object. I'd like to return the object after the for loop is done. Its returning the empty object right away while it does the asynchronous tasks. Makes sense, however I have tried various Promise / async /await combinations hopes of running something once the for loop has completed. I am clearly not understanding what is going on. Is there a better pattern to follow for something like this or am I thinking about it incorrectly?
async function createFormConfig(files: string[]): Promise<object>
return new Promise(resolve => {
const retConfig: any = {};
for (const file of files) {
file.match(matchFilesForFormConfigMap.get('FIELD')) ?
parseCsv(file).then(parsedData => {
retConfig.fields = parsedData.data;
})
: file.match(matchFilesForFormConfigMap.get('FORM'))
? parseCsv(file).then(parsedData => retConfig.formProperties = parsedData.data[0])
: file.match(matchFilesForFormConfigMap.get('PDF'))
? parseCsv(file).then(parsedData => retConfig.jsPdfProperties = parsedData.data[0])
: file.match(matchFilesForFormConfigMap.get('META'))
? parseCsv(file).then(parsedData => {
retConfig.name = parsedData.data[0].name;
retConfig.imgType = parsedData.data[0].imgType;
// console.log(retConfig); <- THIS CONSOLE WILL OUTPUT RETCONFIG LOOKING LIKE I WANT IT
})
: file.match(matchFilesForFormConfigMap.get('PAGES'))
? parseCsv(file).then(parsedData => retConfig.pages = parsedData.data)
: console.log('there is an extra file: ' + file);
}
resolve(retConfig); // <- THIS RETURNS: {}
});
This is the code I'm using to call the function in hopes of getting my 'retConfig' filled with the CSV data.
getFilesFromDirectory(`${clOptions.directory}/**/*.csv`)
.then(async (files) => {
const config = await createFormConfig(files);
console.log(config);
})
.catch(err => console.error(err));
};
First, an async function returns a Promise, so you dont have to return one explicitely.Here is how you can simplify your code:
async function createFormConfig(files: string[]): Promise<object> {
// return new Promise(resolve => { <-- remove
const retConfig: any = {};
// ...
// The value returned by an async function is the one you get
// in the callback passed to the function `.then`
return retConfig;
// }); <-- remove
}
Then, your function createFormConfig returns the config before it has finished to compute it. Here is how you can have it computed before returning it:
async function createFormConfig(files: string[]): Promise<object> {
const retConfig: any = {};
// Return a Promise for each file that have to be parsed
const parsingCsv = files.map(async file => {
if (file.match(matchFilesForFormConfigMap.get('FIELD'))) {
const { data } = await parseCsv(file);
retConfig.fields = data;
} else if (file.match(matchFilesForFormConfigMap.get('FORM'))) {
const { data } = await parseCsv(file);
retConfig.formProperties = data[0];
} else if (file.match(matchFilesForFormConfigMap.get('PDF'))) {
const { data } = await parseCsv(file);
retConfig.jsPdfProperties = data[0];
} else if (file.match(matchFilesForFormConfigMap.get('META'))) {
const { data } = await parseCsv(file);
retConfig.name = data[0].name;
retConfig.imgType = data[0].imgType;
} else if (file.match(matchFilesForFormConfigMap.get('PAGES'))) {
const { data } = await parseCsv(file);
retConfig.pages = data;
} else {
console.log('there is an extra file: ' + file);
}
});
// Wait for the Promises to resolve
await Promise.all(parsingCsv)
return retConfig;
}
async functions already return promises, you don't need to wrap the code in a new one. Just return a value from the function and the caller will receive a promise that resolves to the returned value.
Also, you have made an async function, but you're not actually using await anywhere. So the for loop runs through the whole loop before any of your promises resolve. This is why none of the data is making it into your object.
It will really simplify your code to only use await and get rid of the then() calls. For example you can do this:
async function createFormConfig(files: string[]): Promise<object> {
const retConfig: any = {};
for (const file of files) {
if (file.match(matchFilesForFormConfigMap.get('FIELD')){
// no need for the then here
let parsedData = await parseCsv(file)
retConfig.field = parsedData.data
}
// ...etc
At the end you can just return the value:
return retConfig
Im new to promises and I'm trying to to return the value of a promise like mongoose does but using mongoskin and bluebird. This works with mongoose return User.find().then(users => users). This will return a list of users not a promise in apollo-server resolvers.
I've tried promise generator and async but with no luck. From what I've read a promise always returns a promise so no idea how mongoose is returning a value.
mongodb.js
import Promise from 'bluebird';
import mongoskin from 'mongoskin';
Object.keys(mongoskin).forEach(function (key) {
var value = mongoskin[key];
if (typeof value === 'function') {
Promise.promisifyAll(value);
Promise.promisifyAll(value.prototype);
}
});
Promise.promisifyAll(mongoskin);
export default {
connect (uri) {
return mongoskin.db(uri, {native_parser:true});
}
};
users.js
import mongodb from '../../databases/mongodb';
export default class User {
constructor () {
this.db = mongodb.connect('mongodb://127.0.0.1:27017/test', {native_parser:true});
this.collection = this.db.collection('users');
}
find (query = {}, options = {}) {
const findAsync = () => {
return Promise.resolve().then(() => {
return this.collection.findAsync(query, options);
})
.then((xx) => {
xx.toArray((err, items) => {
if (err) return err;
return items;
});
});
};
async function getData () {
let foo = await findAsync();
return foo;
}
return getData();
}
}
const user = new User();
function bar () {
return user.find().then(x => console.log(x));
}
console.log(bar());
Your code seems overly complicated. I think what you want is this (I didn't promisify mongoskin because it's not very well suited for that; see below):
export default class User {
constructor() {
this.db = mongoskin.connect(...);
this.collection = this.db.collection('users');
}
find (query = {}, options = {}) {
return new Promise((resolve, reject) => {
this.collection.find(query, options).toArray((err, items) => {
if (err) return reject(err);
resolve(items);
});
});
}
}
I have to say that Mongoskin feels pretty outdated. It's a skin on top of the official mongodb driver, which is pretty decent nowadays (it supports promises out-of-the-box, for one, something that Mongoskin doesn't propagate).
If you do want to promisify, then I should say that the following is a promise anti-pattern:
return Promise.resolve().then(() => {
return this.collection.findAsync(query, options);
}).then(...)
You can rewrite it to this:
return this.collection.findAsync(query, options).then(...);
However, the toArray() makes things difficult again, because for that you do need to create a new promise, so the code becomes something like this:
return this.collection.findAsync(query, options).then((cursor) => {
return new Promise((resolve, reject) => {
cursor.toArray((err, items) => {
if (err) return reject(err);
resolve(items);
});
});
});
Which doesn't look very pretty at all, hence my choice to not promisify and just use callbacks in this case (although I'm sure that Bluebird has some nice tools that may the above code easier to look at, but still...).