Using async/await with callback functions - javascript

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.

Related

Why can I not return an array of objects in an async function?

In node.js I am trying to get a list of bid and ask prices from a trade exchange website (within an async function). Within the foreach statement I can console.info() the object data(on each iteration) but when I put all of this into an array and then return it to another function it passes as 'undefined'.
const symbolPrice = async() => {
let symbolObj = {}
let symbolList = []
await bookTickers((error, ticker) => {
ticker.forEach(symbol => {
if (symbol.symbol.toUpperCase().startsWith(starts.toUpperCase())) {
symbolObj = {
symbol: symbol.symbol,
bid: symbol.bidPrice,
ask: symbol.askPrice
}
console.info(symbolObj);
}
symbolList.push(symbolObj)
});
const results = Promise.all(symbolList)
return results;
});
}
const symbolPriceTest = async() => {
const res = await symbolPrice(null, 'ETH', true);
console.log(res)
}
I have tried pretty much everything I can find on the internet like different awaits/Promise.all()'s. I do admit I am not as familiar with async coding as I would like to be.
So, if the basic problem here is to call bookTickers(), retrieve the asynchronous result from that call, process it and then get that processed result as the resolved value from calling symbolPrice(), then you can do that like this:
const { promisify } = require('util');
const bookTickersP = promisify(bookTickers);
async function symbolPrice(/* declare arguments here */) {
let symbolList = [];
const ticker = await bookTickersP(/* fill in arguments here */);
for (let symbol of ticker) {
if (symbol.symbol.toUpperCase().startsWith(starts.toUpperCase())) {
symbolList.push({
symbol: symbol.symbol,
bid: symbol.bidPrice,
ask: symbol.askPrice
});
}
}
return symbolList;
}
async function symbolPriceTest() {
const res = await symbolPrice(null, 'ETH', true);
console.log(res)
}
Things to learn from your original attempt:
Only use await when you are awaiting a promise.
Only use Promise.all() when you are passing it an array of promises (or an array of a mixture of values and promises).
Don't mix plain callback asynchronous functions with promises. If you don't have a promise-returning version of your asynchronous function, then promisify it so you do (as shown in my code above with bookTickersP().
Do not guess with async and await and just throw it somewhere hoping it will do something useful. You MUST know that you're awaiting a promise that is connected to the result you're after.
Don't reuse variables in a loop.
Your original implementation of symbolPrice() had no return value at the top level of the function (the only return value was inside a callback so that just returns from the callback, not from the main function). That's why symbolPrice() didn't return anything. Now, because you were using an asynchronous callback, you couldn't actually directly return the results anyway so other things had to be redone.
Just a few thoughts on organization that might be reused in other contexts...
Promisify book tickers (with a library, or pure js using the following pattern). This is just the api made modern:
async function bookTickersP() {
return new Promise((resolve, reject) => {
bookTickers((error, ticker) => {
error ? reject(error) : resolve(ticker);
})
});
}
Use that to shape data in the way that the app needs. This is your app's async model getter:
// resolves to [{ symbol, bid, ask }, ...]
async function getSymbols() {
const result = await bookTickersP();
return result.map(({ symbol, bidPrice, askPrice }) => {
return { symbol: symbol.toUpperCase(), bid: bidPrice, ask: askPrice }
});
}
Write business logic that does things with the model, like ask about particular symbols:
async function symbolPriceTest(search) {
const prefix = search.toUpperCase();
const symbols = await getSymbols();
return symbols.filter(s => s.symbol.startsWith(prefix));
}
symbolPriceTest('ETH').then(console.log);

Why objects key still has unknow promise , though I have used await for mapping

I'm new to javascript. as I know using array.map over async function returns array of promises.
and I can use await Promise.all to reolve all promise and it returns me data.
I want to understand how can I use asyn function inside array of object's key.
As I know using async function it never block as execution for unimportant line, ( here other execution is not dependent on url , so I'm trying to make it asynchronous)
async function showPost() {
const posts = [
{ title: 'a', url: ['q', 'o'] },
{ title: 'b', url: ['t', 'y'] },
];
const formattedPost = await formatPosts(posts);
console.log(formattedPost);
}
const formatPosts = async (posts) => {
const formattedPosts = await Promise.all( // NOTE: await promise.all on map
posts.map(async (post) => {
post.url = addUrl(post.url); //NOTE: here if I don' add await here post.url is Promise<Unknown>
return post;
})
);
return formattedPosts;
};
const addUrl = async (d) => {
const mm = await Promise.all(d.map((item) => item + '-dummyUrl'));
return mm;
};
showPost();
**CODE 1 without await , but inside await Promise.all **
CODE 2 with AWAIT on addURL call I get output
Why is it not resolving though it is inside await promise.all
thanks for help in advance
When you call Promise.all(), you're resolving the promises returned by the map() function. When you don't add await to addURL(), you're replacing that value in the object with a promise object, which isn't resolved by Promise.all().
Promises are still promises, even after they resolve.
On top of this, Array.prototype.map() isn't an async function, so addURL() doesn't need to be async, which makes adding await pointless.
I know using array.map over async function returns array of promises, and I can use await Promise.all to resolve all promise and it returns me data.
This will just wait for the promises that are elements of the array that was passed to Promise.all, not "all promises" anywhere.
Notice that without the await, you don't need to make the map callback an async function, and you don't need to wait for the promises in the mapped array then; your current code is exactly equivalent to
const formatPosts = async (posts) => {
const formattedPosts = posts.map((post) => {
post.url = addUrl(post.url);
return post;
});
return formattedPosts;
};
Why objects key still has unknown promise
Because you assigned a promise object to the object property. Never did you instruct anything to wait for that promise. This is what would be fixed by doing
post.url = await addUrl(post.url);

Why is findOneAndRemove sometimes passing null to doc in the callback?

I am using the following function which is supposed to have a callback input:
https://mongoosejs.com/docs/api.html#model_Model.findOneAndRemove
I have the following Mutation object that is supposed to delete a particular medicine from the database, and then remove all of the instances of its medicineId from the arrays of all of the Day entries that contain the id.
deleteMedicine: {
type: MedicineType,
args: {
id: { type: new GraphQLNonNull(GraphQLID) }
},
async resolve (parent, args) {
let res = Medicine.findOneAndRemove({ _id: args.id }, async (err, doc) => {
if (err === null && doc === null || doc === null) {
return;
} else if (err) {
console.error(err);
return;
} else {
return await Promise.all(doc.dayNames.map(dayName => {
return DayOfWeek.findByIdAndUpdate(dayName, { $pull: { medicineIds: doc._id }})
.catch((error1) => console.error(error1));
})).catch((error2) => console.error(error2));
}
});
await res;
return res;
}
}
findOneAndRemove successfully deletes the document that has args.id in the Medicine collection, but when It calls the callback function, sometimes it passes null to doc and so the callback function doesn't consistently execute properly.
Also I am getting an unhandled error warning even though I am catching all errors. \
I added logic to handle when err and doc are both null according to this post:
https://github.com/Automattic/mongoose/issues/5022
You should only uses Promises, not callbacks, with GraphQL.js. If the library you're using doesn't support Promises, then you'll need to wrap each callback in a Promise. However, mongoose has great Promise support -- you just omit the callback from your method call and the call will return a Promise.
To avoid "callback hell" when using Promises and issues with properly chaining your Promises, it's generally preferable to utilize async/await syntax. Your cleaned up resolver would look something like:
async resolve (parent, args) {
// Omit the callback to get a Promise and then await the result
const medicine = await Medicine.findOneAndRemove({ _id: args.id })
// If a medicine with that ID couldn't be found, we'll get a null
if (medicine) {
// Make sure we await the Promise returned by Promise.all
await Promise.all(doc.dayNames.map(dayName => DayOfWeek.findByIdAndUpdate(
dayName,
{ $pull: { medicineIds: doc._id } }
))
}
// Make sure we return the appropriate result depending on the field's type
return medicine
}
Typically when dealing with Promises, we use the catch method (or try/catch with async/await) in order to handle errors. However, this is only necessary inside a resolver if you want to mask or otherwise format your error -- otherwise, GraphQL will happily catch any errors for you and return them in the response. However, this only works if your Promises are correctly chained and the resolver returns a Promise. If you end up with an "orphan" Promise that's not awaited, you'll see unhandled error warnings.

Async/await while performing CRUD operations in mongoose

I am performing a delete operation using findByIdAndRemove mongoose query method and want to make sure that the deleted doc gets returned. I googled it and found that we pass a callback to findByIdAndRemove in which we pass err, doc args and if delete is successful the deleted value gets returned in doc which we can return from the fn call. But I am somewhat confused in async and await. I have deleteUserTask fn which internally calls async deleteUserTask fn which is promise based and expects a promise in return which when resolved (.then) I can get the value of the task which was deleted.
I am not sure how to return the value of doc from my async deleteUserTask fn which will be then resolved in .then promise and I can use it?
With below approach the value returned is null as I am not returning the doc value at all.
function deleteUserTask(req, res, next) {
taskService
.deleteUserTask(req.params.taskId)
.then(task => {
console.log(
"Delete task request took " + (moment.now() - req.requestTime + " ms")
);
// below gives null
console.log(task);
res.json({ task });
})
.catch(err => next(err));
}
taskService.js
async function deleteUserTask(id) {
return await Task.findByIdAndRemove(id, function(err, doc) {
if (err) {
return err;
}
});
}
A callback shouldn't be used with findByIdAndRemove in case promises are used. It should be:
async function deleteUserTask(id) {
return await Task.findByIdAndRemove(id);
}
At this point there are no benefits from using async function, it could be simplified to:
function deleteUserTask(id) {
return Task.findByIdAndRemove(id);
}

Typescript async function wrapping promise

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! :)

Categories

Resources