Typescript async function wrapping promise - javascript

I'm curerntly playing with Typescript and ionic and also new to async-await on javascript. I'm trying to get a good undestanding of promises but I can't figure out how to wrap methods which call multiple promises to return a promise. I'll try to elaborate better:
Basically, I have an object which can create a SQLite database, and when the create() method is invoked, it would return a Promise with the actual database object once it has been created.
Then, when the promise resolves and the DB object is returned, I need to use it to execute some statements in a transaction to create all the tables, and a new promise is returned when I invoke the execution of all the statements in the transaction.
Then when the transaction is finished and everything went good, I need to assign the database object to a class property and set a flag indicating database is created and ready to go.
So I thought it would be a good idea to wrap this database initialization stuff in a method called createDatabase() or something like that, which returns a Promise<SQLiteObject>, where SQLiteObject represents the database. This method would be called on initialization and should return the SQLiteObject representing the database once everything went OK, or throw an error which I would log in the .catch() method of the Promise.
I have a basic understanding of promises and how to use then() and catch() methods but I'm a bit confused when I have to create the database, then do something else when the promise resolves, and when everything is done, return a Promise containing the DB object, which is an instance of the class SQLiteObject.
CODE
Below is my current Typescript code. It's not valid typescript as I don't know how to return the Promise<SQLiteObject> from the async function.
async createDatabase(): Promise<SQLiteObject> {
this.sqlite.create({
name: this.dbName,
location: this.dbLocation
}).then( (db: SQLiteObject) => {
// Insert all tables.
let createTableParam: string = `CREATE TABLE IF NOT EXISTS param (name PRIMARY KEY NOT NULL, value TEXT)`;
let createTableNews: string = `CREATE TABLE IF NOT EXISTS news (id PRIMARY KEY NOT NULL,title TEXT,
content TEXT, image TEXT, date DATE)`;
db.transaction(tx => {
tx.executeSql(createTableParam);
tx.executeSql(createTableNews);
// Add here more tables to create if needed
}
)
.then( () => {
console.log('Tables were created');
this.isActive = true;
})
.catch(error => {
console.log(`Error creating tables - ${error}`);
});
}).catch(
error => console.log(`Error at SQLite initialization - ${error}`)
);
}
RESEARCH SO FAR
I have read this question here which seems related but I am
confused on how to implement on my code
Checked async/await on MDN
This question here also seems related but still don't really understand how to do it.
Typescript promises at "Typescript Deep Dive"
Async/Await at "Typescript Deep Dive"

You used async, so that means you can use await inside the function any time you have a Promise and write the code almost as though it were synchronous.
async createDatabase(): Promise<SQLiteObject> {
let db: SQLiteObject;
try {
db = await this.sqlite.create({
name: this.dbName,
location: this.dbLocation
});
} catch(error) {
console.log(`Error at SQLite initialization - ${error}`);
return;
);
// Insert all tables.
let createTableParam: string = `CREATE TABLE IF NOT EXISTS param (name PRIMARY KEY NOT NULL, value TEXT)`;
let createTableNews: string = `CREATE TABLE IF NOT EXISTS news (id PRIMARY KEY NOT NULL,title TEXT,
content TEXT, image TEXT, date DATE)`;
try {
await db.transaction(tx => {
tx.executeSql(createTableParam);
tx.executeSql(createTableNews);
// Add here more tables to create if needed
}
);
console.log('Tables were created');
this.isActive = true;
return db;
catch(error) {
console.log(`Error creating tables - ${error}`);
});
}
Without the await you need to be sure to return that initial promise.
return this.sqlite.create({...
and then again further down you can return the db object:
this.isActive = true;
return db;
Also you should avoid nesting the .then() handlers: when you get another promise just return it from the first handler and chain another .then on the end:
createDatabase(): Promise<SQLiteObject> {
let database: SQLiteObject = null;
return this.sqlite.create({
name: this.dbName,
location: this.dbLocation
})
.catch(error => console.log(`Error at SQLite initialization - ${error}`))
.then( (db: SQLiteObject) => {
// Insert all tables.
let createTableParam: string = `CREATE TABLE IF NOT EXISTS param (name PRIMARY KEY NOT NULL, value TEXT)`;
let createTableNews: string = `CREATE TABLE IF NOT EXISTS news (id PRIMARY KEY NOT NULL,title TEXT,
content TEXT, image TEXT, date DATE)`;
database = db;
return db.transaction(tx => {
tx.executeSql(createTableParam);
tx.executeSql(createTableNews);
// Add here more tables to create if needed
}
);
})
.then( () => {
console.log('Tables were created');
this.isActive = true;
return database;
})
.catch(error => console.log(`Error creating tables - ${error}`));

It appears you're not resolving the promise.
A promise must be resolved or rejected so a async function is capable of responding with a value.
From TypeScript Deep Dive:
const promise = new Promise((resolve, reject) => {
resolve(123);
});
promise.then((res) => {
console.log('I get called:', res === 123); // I get called: true
});
promise.catch((err) => {
// This is never called
});
So in my opinion you should create a promise, and when the DB is created and everything resolve it, if there's a problem with the DB creation, reject it.
Remember you can chain promises, so you could chain them as you create your database.
From TypeScript Deep Dive:
The chain-ability of promises is the heart of the benefit that promises provide. Once you have a promise, from that point on, you use the then function to create a chain of promises.
Check this URL for more info about promises
Hope it helps! :)

Related

Using async/await with callback functions

I'm trying to build a service layer on top of mongo. I have a User object which has an array of referenced Achievements.
After I've authenticated the user by JWT or some other means, I enter the service layer and query the relationship as follows.
findForUser(userId: string | Types.ObjectId): Promise<Achievement[]> {
return new Promise((resolve) => {
UserSchema.findOne({ _id: userId },
async (err: any, user: User) => {
const AchievementMap: Achievement[] = [];
if (err) throw new Error(err);
user.achievements?.forEach((a) => {
// #ts-ignore
AchievementMap.push(a);
});
resolve(AchievementMap);
});
});
}
What is the async/await approach to returning the result of the callback method passed into UserSchema.findOne?
findOne returns an awaitable object - so you don't need to pass a callback to it. Don't try mixing callbacks with async/await. The only way to return the value from the callback, as a result of the constructed promise is by using resolve (only available in the promise executor).
Instead, make it all async - functionally similar but far cleaner.
async findForUser(userId: string | Types.ObjectId): Promise<Achievement[]> {
const user = await UserSchema.findOne({ _id: userId });
const AchievementMap: Achievement[] = [];
user.achievements?.forEach(a => {
AchievementMap.push(a);
});
return AchievementMap;
}
What is the async/await approach to returning the result of the callback
The async and await keywords are tools to manage promises. They aren't tools to manage callbacks.
The entire point of the code you have in the question is to wrap a promise based API around a function that deals in callbacks.
Now you have a promise you can await the return value of findForUser.
You can't use async and await instead of creating a promise.

Function Return Undefined, Expected Promise or Value

New to cloud functions and promises. I've tried adding promises in different locations but still getting the message in logs. First, I'm not sure where I should be adding the promise and second whether I should be returning nothing. I don't need to execute another function after I call match (and create channel if condition met). There will be multiple users though triggering onCreate so I want to make sure things are executed one user at a time.
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp();
//every time user added to liveLooking node
exports.command = functions.database
.ref('liveLooking/{uid}')
.onCreate((snap, context) => {
const uid = context.params.uid
match(uid)
})
function match(uid) {
let m1uid, m2uid
admin.database().ref('liveChannels').transaction((data) => {
//if no existing channels then add user to liveChannels
if (data === null) {
console.log(`${uid} waiting for match`)
return { uid: uid }
}
else {
m1uid = data.uid
m2uid = uid
if (m1uid === m2uid) {
console.log(`$m1uid} tried to match with self!`)
//match user with liveChannel user
} else {
console.log(`matched ${m1uid} with ${m2uid}`)
createChannel(m1uid, m2uid)
return null
}
}
},
(error, committed, snapshot) => {
if (error) {
throw error
}
else {
return {
committed: committed,
snapshot: snapshot
}
}
},
false)
}
function createChannel(uid1, uid2) {
// Add channels for each user matched
const channel_id = uid1+uid2
console.log(`starting channel ${channel_id} with uid1: ${uid1}, uid2: ${uid2}`)
const m_state1 = admin.database().ref(`liveUsers/${uid1}`).set({
channel: channel_id
})
const m_state2 = admin.database().ref(`liveUsers/${uid2}`).set({
channel: channel_id
})
}
Edit 1 - I tried changing the transaction to use await so it will change userLives node only after the transaction. Getting these two warnings. 1) Expected to return a value at the end of async function 'match'. 2) At ***return Arrow function expected a return value. I'm not trying to change anything under LiveChannels if use matches with self. Not sure how to fix that warning. 3) Still getting function return undefined, expected promise or value in the logs - I think for command function.
async function match(uid) {
let m1uid, m2uid;
let createChannel = false
try {
const transactionResult = await admin
.database()
.ref("liveChannels")
.transaction(
(data) => {
if (data === null) {
console.log(`${uid} waiting for match`)
return { uid: uid }
}
else {
m1uid = data.uid
m2uid = uid
if (m1uid === m2uid) {
console.log(`$m1uid} tried to match with self!`)
***return***
} else {
console.log(`matched ${m1uid} with ${m2uid}`)
createChannel = true
return {}
}
}
},
(error, committed, snapshot) => {
if (error) {
throw error
}
else {
return {
committed: committed,
snapshot: snapshot
}
}
},
false
);
if (transactionResult) {
if (createChannel) {
const channel_id = m1uid + m2uid
console.log(`starting channel ${channel_id} with uid1: ${m1uid}, uid2: ${m2uid}`)
const m_state1 = admin.database().ref(`liveUsers/${m1uid}`).set({
channel: channel_id
})
const m_state2 = admin.database().ref(`liveUsers/${m2uid}`).set({
channel: channel_id
})
return Promise.all([m_state1, m_state2])
}
}
} catch (err) {
throw new Error(err);
}
}
You are not correctly managing the life cycle of your Cloud Function. You need to wait that all the asynchronous tasks (i.e. the calls to Firebase asynchronous methods) are completed before indicating to the platform that it can clean up the function. This should be done by returning a Promise, and more precisely, since you chain several asynchronous method calls, by returning the promises chain.
However, there is another problem in your Cloud Function, with the way you write the Transaction. A Transaction atomically modifies the data at the location (i.e. the Reference) you call the transaction on.
In your code, you call the transaction on ref('liveChannels') but inside the transaction, you modify data at other locations: ref(`liveUsers/${uid1}`) and ref(`liveUsers/${uid2}`).
This is not the correct way: If you want to encapsulate all these operations in one Transaction, you need to call the Transaction on a database node which contains all the nodes/locations you want to change, i.e. a common parent/ancestor node of liveChannels, liveUsers/${uid1} and liveUsers/${uid2}.
If I'm not mistaking the only common parent/ancestor node for those three nodes is the DB root node. It is probably not a good idea to call a Transaction on the DB root node, in particular if you have a lot of users using your app concurrently. You should probably adapt your data model (DB structure) to avoid that.
Also note that instead of using the onComplete callback of the Tansaction to handle its success and failure, you should use the Promise returned by the Transaction, in order to correctly use it in the Promise chain (see first paragraph).

How do I return a value in a promise? (Firebase.get())

I've been struggling for the past four hours on this. I am writing a function to see if a property named "data" exists in my Firebase storage. If it does, I want to do one thing, if it doesn't I want to do something else. However, I cannot figure out how this asynchronous stuff works for the life of me. I simplified my code below. Basically I just want to wait for the data to be fetched before I reach the if/else. I've been playing around with different options but keep getting errors or some other issue. The code below is the closest I've gotten to working where the code doesn't crash but even if "data" does not exist in the Firestore, I'm always going through the else clause and I don't know why. Can someone help me figure out what I am doing wrong?
const fetchDataFromDB = async (user) => {
let query = db
.collection("users")
.doc(user)
.get()
.then((doc) => {
doc.data();
console.log(doc.data().data);
});
return await query;
};
export const getSchedule = (miles, user) => {
const firebaseData = fetchDataFromDB(user);
console.log(firebaseData);
// WAIT FOR FETCH BEFORE CONTINUING
if (!firebaseData) {
console.log("NOT getting data from FB");
return;
} else {
console.log("getting data from FB");
return;
}
};
Change up the code as follows:
const fetchDataFromDB = (user) => {
return db
.collection("users")
.doc(user)
.get()
.then((doc) => {
const data = doc.data();
console.log(data);
return data;
});
};
export const getSchedule = async (miles, user) => {
const firebaseData = await fetchDataFromDB(user);
console.log(firebaseData);
if (!firebaseData) {
console.log("NOT getting data from FB");
return;
} else {
console.log("getting data from FB");
return;
}
};
The point to remember about async await is that it doesn't really make asynchronous calls synchronous, it just makes them look that way so that your code is a bit less unwieldy with wrapped promises and the like. Every async function returns a promise, so if you want to deal with what it returns, you need to either deal with the promise directly (using .then...), or by using another await. In the latter case, you of course need to declare the consuming function as async as well.
With regards to the first function, there's no need for the async await there. Just return the promise. (Thanks #charlieftl for pointing out the problem in that code)

Chaining different API calls in a Vue component including one with a for loop

I'm trying to understand how to chain two different API calls including one with a for loop in a 'notes' Vue component. I have a really basic experience of promises and I'm looking to improve.
I'm making a first API call to get all the notes and pushing them into an array using a Vuex mutation. During that first API call I'm also mapping the different users emails into an Object.
Using this mapped object, I'm making a second API call inside a for loop to get all the users avatars.
Here's what the first API call looks like :
getAllNotesAPI(entity) {
noteService.getNotes(entity)
.then((response) => {
if (response.data.length === '0') {
// Set hasData to false if the response is 0
this.hasData = false;
} else {
// Push data into the note array using a store mutation
this.setAllNotes(response.data);
}
// Mapping all users emails into 'userEmails'
this.userEmails = [...new Set(response.data.map(x => x.userEmail))];
// Calling my second API call here to get all the avatars associated with these emails
for (let i = 0; i < this.userEmails.length; i++) {
this.getAvatarAPI(this.userEmails[i])
}
})
.catch((error) => {
console.log(error);
})
.finally(() => {
this.endLoader('notes');
});
},
this.getAvatarAPI is the second API call which looks like this :
getAvatarAPI(login) {
userService.getAvatar(login)
.then((response) => {
let newAvatar = {
userEmail: login,
picture: response.data.picture
};
// Push the response into a userAvatar Object using a store mutation
this.setUserAvatar(newAvatar);
}).catch((error) => {
console.log(error)
})
},
I've tried using async / await but couldn't figure out how to bind this inside of an async function (this.getAvatarAPI(this.userEmails)) was undefined, I've tried chaining using multiples then but couldn't figure out how to : get all my notes then all my avatars then end the 'note' loader once both those API calls are done.
If any of you could give me some pointers or the beginning of an answer that would be truly appreciated !
First whilst not related to your problem, avoid for loop when non necessary:
Do you need the i index?
for (let i = 0; i < this.userEmails.length; i++) {
this.getAvatarAPI(this.userEmails[i])
}
no. You need the userMail. Then
this.userEmails.forEach(userMail => {
this.getAvatarAPI(userEmail)
})
Now, to synchronize promises, you need to return a promise (let's not talk about async yet)
make getAvatarAPI return a promise
getAvatarAPI(login) {
return userService.getAvatar(login).then(blabla) // notice the return here
retrieve the promises of getAvatar API
let promises = this.userEmails.map(userMail => {
return getAvatarAPI(userEmail)
})
return after all promises have fulfilled
let promises = this.userEmails.map(userMail => {
return getAvatarAPI(userEmail)
})
return Promise.all(promises)
On a side note with async/await
If you use it you are not forced anymore to write return, you need to write async/await though
The underlying idea stay the same. Specifying the async keywords says that your function will return a promise-like.
e.g
async function p () {
return 5
}
p.then(x => console.log(x)) // does print 5 even though we didn't explicitely write return Promise.resolve(5)
Now you have to ensure you await the async function when you call it:
getAvatarAPI: async login => {
return userService.getAvatar(login).then(blabla)
}
// DO NOT do it
this.userEmails.forEach(userMail => {
return await this.getAvatarAPI(userEmail)
})
In forEach loop above, you will do your getAvatarAPI call in sequence because await "stops" iterating as long as getAvatarAPI has not resolved.
The proper way would be
getAllNotesAPI: async entity => {
try { // notice the necesary try-catch block
const response = await noteService.getNotes(entity)
blabla
let promises = this.userEmails.map(userMail => {
return this.getA...
})
let result = await Promise.all(promises)
// eventually return result, or simply await Promise... without lefthand-side assignment
} catch (error) {
console.log(error);
}
console.log(this.end('loader'))
}

Promise hell, Anti-pattern and Error Handling

I am a newbie in node.js environment. I read a lot of source about implementing Promises and chaining them together. I am trying to avoid anti-pattern implementation but I could not figure it out how can I do it.
There is a User Registration flow in the system.
First, I check the username.
if there is no user in the DB with this username I create a User model and save it to the DB.
Can you kindly see my comment inline ?
app.js
RegisterUser("existUser","123").then(user=>{
//send response, etc
}).catch(er => {
console.log(er);
//log error,send proper response, etc
// Should I catch error here or inner scope where RegisterUser implemented ?
});
userService.js
function RegisterUser(username, password) {
return new Promise((resolve, reject) => {
GetUser(username)
.then(user=>{
if(user)reject(new Error("User Exists"));
else{
resolve(SaveUser(username,password))// Is that ugly?
//what if I have one more repository logic here ?
//callback train...
}
})
.then(user => {
resolve(user);//If I do not want to resolve anything what is the best practice for it, like void functions?
}).catch(err=>{
console.log(err); // I catch the db error what will I do now :)
reject(err);// It seems not good way to handle it, isn't it ?
// Upper promise will handle that too. But I dont know how can I deal with that.
});;
});
}
repository.js
function GetUser(username) {
return new Promise((resolve, reject) => {
if (username === "existUser")
resolve("existUser");
else resolve("");
});
}
function SaveUser(username, password) {
return new Promise((resolve, reject) => {
reject(new Error("There is a problem with db"));//assume we forgot to run mongod
});
}
The code above seems awful to me.
I thought I need to define some method that can chain after GetUser method.
like
GetUser(username)
.then(SaveUserRefined)// how could it know other parameters like password, etc
.then(user=> {resolve(user)}) // resolve inside then ? confusing.
.catch(er=>//...);
I feel I do anti-pattern here and create "promise hell"
How could a simple flow like that implemented.
Validate username and save it.
Thanks.
Yes, that's the Promise constructor antipattern! Just write
function registerUser(username, password) {
return getUser(username).then(user => {
if (user) throw new Error("User Exists");
else return saveUser(username,password)
});
}
Notice I also lowercased your function names. You should only capitalise your constructor functions.
Should I catch error here or inner scope where registerUser implemented?
You should catch errors where you can handle them - and you should handle errors at the end of the chain (by logging them, usually). If registerUser doesn't provide a fallback result, it doesn't need to handle anything, and it usually also doesn't need to log the error message on its own (if you want to, see here for an example).
See also Do I always need catch() at the end even if I use a reject callback in all then-ables?.
If you already are working with promises, then there's no need to create your own. When you call .then on a promise and provide a function saying what to do, that creates a new promise, which will resolve to whatever the function returns.
So your registerUser function should look something like this:
function registerUser(username, password) {
return getUser(username)
.then(user => {
if (user) {
throw new Error('User Exists');
}
return saveUser(username, password)
});
}
and you use it like this:
registerUser('starfox', 'doABarrelRoll')
.catch(error => console.log(error);
Note that if SaveUser causes an error, it will end up in this final .catch, since i didn't put any error handling inside registerUser. It's up to you to decide where you want to handle the errors. If it's something recoverable, maybe registerUser can handle it, and the outside world never needs to know. But you're already throwing an error if the user name exists, so the caller will need to be aware of errors anyway.
Additionally your getUser and saveUser functions might also be able to avoid creating their own promises, assuming the real implementation calls some function that returns a promise.
Your should use async/await syntax to avoid Promise Hell.
Change your code like this
/**
* This is a promise that resolve a user or reject if GetUser or SaveUser reject
*/
async function RegisterUser (username, password) {
var user = await GetUser(username)
if (user)
return user;
var userSaved = await SaveUser(username,password)
return userSaved
}
If you use RegisterUser inside a async function just code
async function foo () {
try {
var usr = await RegisterUser('carlos', 'secret123')
return usr
} catch (e) {
console.error('some error', e)
return null
}
}
Or if you use it like a promise
RegisterUser('carlos', 'secret123')
.then(usr => console.log('goood', usr))
.catch(e => console.error('some error', e))

Categories

Resources