This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I need to fetch id of user from collection 'users' by calling a function and return it's value.
fetchId = (name) => {
User.findOne({name: name}, (err, user) => {
return user._id;
});
};
But this implementation returns null. What is the way to fix it?
following your example, if you don't want to use promises, you can simply pass a callback from the caller and invoke the callback when you have the result since the call to mongo is asynchronous.
fetchId = (name, clb) => {
User.findOne({name: name}, (err, user) => {
clb(user._id);
});
};
fetchId("John", id => console.log(id));
Otherwise you can use the promise based mechanism omitting the first callback and return the promise to the caller.
fetchId = name => {
return User.findOne({name: name}).then(user => user.id);
};
fetchId("John")
.then(id => console.log(id));
A third way is a variation of suggestion #2 in #Karim's (perfectly good) answer. If the OP wants to code it as if results are being assigned from async code, an improvement would be to declare fetchId as async and await its result...
fetchId = async (name) => {
return User.findOne({name: name}).then(user => user.id);
};
let someId = await fetchId("John");
console.log(id)
edit
For any method on an object -- including a property getter -- that works asynchronously, the calling code needs to be aware and act accordingly.
This tends to spread upward in your system to anything that depends on caller's callers, and so on. We can't avoid this, and there's no syntactic fix (syntax is what the compiler sees). It's physics: things that take longer, just take longer. We can use syntax to partially conceal the complexity, but we're stuck with the extra complexity.
Applying this to your question, say we have an object representing a User which is stored remotely by mongo. The simplest approach is to think of the in-memory user object as unready until an async fetch (findOne) operation has completed.
Under this approach, the caller has just one extra thing to remember: tell the unready user to get ready before using it. The code below employs async/await style syntax which is the the most modern and does the most to conceal -- but not eliminate :-( -- the async complexity...
class MyMongoUser {
// after new, this in-memory user is not ready
constructor(name) {
this.name = name;
this.mongoUser = null; // optional, see how we're not ready?
}
// callers must understand: before using, tell it to get ready!
async getReady() {
this.mongoUser = await myAsyncMongoGetter();
// if there are other properties that are computed asynchronously, do those here, too
}
async myAsyncMongoGetter() {
// call mongo
const self = this;
return User.findOne({name: self.name}).then(result => {
// grab the whole remote object. see below
self.mongoUser = result;
});
}
// the remaining methods can be synchronous, but callers must
// understand that these won't work until the object is ready
mongoId() {
return (this.mongoUser)? this.mongoUser._id : null;
}
posts() {
return [ { creator_id: this.mongoId() } ];
}
}
Notice, instead of just grabbing the mongo _id from the user, we put away the whole mongo object. Unless this is a huge memory hog, we might as well have it hanging around so we can get any of the remotely stored properties.
Here's what the caller looks like...
let joe = new MyMongoUser('joe');
console.log(joe.posts()) // isn't ready, so this logs [ { creator_id: null } ];
await joe.getReady();
console.log(joe.posts()) // logs [ { creator_id: 'the mongo id' } ];
Related
I am looking at https://www.promisejs.org/patterns/ and it mentions it can be used if you need a value in the form of a promise like:
var value = 10;
var promiseForValue = Promise.resolve(value);
What would be the use of a value in promise form though since it would run synchronously anyway?
If I had:
var value = 10;
var promiseForValue = Promise.resolve(value);
promiseForValue.then(resp => {
myFunction(resp)
})
wouldn't just using value without it being a Promise achieve the same thing:
var value = 10;
myFunction(10);
Say if you write a function that sometimes fetches something from a server, but other times immediately returns, you will probably want that function to always return a promise:
function myThingy() {
if (someCondition) {
return fetch('https://foo');
} else {
return Promise.resolve(true);
}
}
It's also useful if you receive some value that may or may not be a promise. You can wrap it in other promise, and now you are sure it's a promise:
const myValue = someStrangeFunction();
// Guarantee that myValue is a promise
Promise.resolve(myValue).then( ... );
In your examples, yes, there's no point in calling Promise.resolve(value). The use case is when you do want to wrap your already existing value in a Promise, for example to maintain the same API from a function. Let's say I have a function that conditionally does something that would return a promise — the caller of that function shouldn't be the one figuring out what the function returned, the function itself should just make that uniform. For example:
const conditionallyDoAsyncWork = (something) => {
if (something == somethingElse) {
return Promise.resolve(false)
}
return fetch(`/foo/${something}`)
.then((res) => res.json())
}
Then users of this function don't need to check if what they got back was a Promise or not:
const doSomethingWithData = () => {
conditionallyDoAsyncWork(someValue)
.then((result) => result && processData(result))
}
As a side node, using async/await syntax both hides that and makes it a bit easier to read, because any value you return from an async function is automatically wrapped in a Promise:
const conditionallyDoAsyncWork = async (something) => {
if (something == somethingElse) {
return false
}
const res = await fetch(`/foo/${something}`)
return res.json()
}
const doSomethingWithData = async () => {
const result = await conditionallyDoAsyncWork(someValue)
if (result) processData(result)
}
Another use case: dead simple async queue using Promise.resolve() as starting point.
let current = Promise.resolve();
function enqueue(fn) {
current = current.then(fn);
}
enqueue(async () => { console.log("async task") });
Edit, in response to OP's question.
Explanation
Let me break it down for you step by step.
enqueue(task) add the task function as a callback to promise.then, and replace the original current promise reference with the newly returned thenPromise.
current = Promise.resolve()
thenPromise = current.then(task)
current = thenPromise
As per promise spec, if task function in turn returns yet another promise, let's call it task() -> taskPromise, well then the thenPromise will only resolve when taskPromise resolves. thenPromise is practically equivalent to taskPromise, it's just a wrapper. Let's rewrite above code into:
current = Promise.resolve()
taskPromise = current.then(task)
current = taskPromise
So if you go like:
enqueue(task_1)
enqueue(task_2)
enqueue(task_3)
it expands into
current = Promise.resolve()
task_1_promise = current.then(task_1)
task_2_promise = task_1_promise.then(task_2)
task_3_promise = task_2_promise.then(task_3)
current = task_3_promise
effectively forms a linked-list-like struct of promises that'll execute task callbacks in sequential order.
Usage
Let's study a concrete scenario. Imaging you need to handle websocket messages in sequential order.
Let's say you need to do some heavy computation upon receiving messages, so you decide to send it off to a worker thread pool. Then you write the processed result to another message queue (MQ).
But here's the requirement, that MQ is expecting the writing order of messages to match with the order they come in from the websocket stream. What do you do?
Suppose you cannot pause the websocket stream, you can only handle them locally ASAP.
Take One:
websocket.on('message', (msg) => {
sendToWorkerThreadPool(msg).then(result => {
writeToMessageQueue(result)
})
})
This may violate the requirement, cus sendToWorkerThreadPool may not return the result in the original order since it's a pool, some threads may return faster if the workload is light.
Take Two:
websocket.on('message', (msg) => {
const task = () => sendToWorkerThreadPool(msg).then(result => {
writeToMessageQueue(result)
})
enqueue(task)
})
This time we enqueue (defer) the whole process, thus we can ensure the task execution order stays sequential. But there's a drawback, we lost the benefit of using a thread pool, cus each sendToWorkerThreadPool will only fire after last one complete. This model is equivalent to using a single worker thread.
Take Three:
websocket.on('message', (msg) => {
const promise = sendToWorkerThreadPool(msg)
const task = () => promise.then(result => {
writeToMessageQueue(result)
})
enqueue(task)
})
Improvement over take two is, we call sendToWorkerThreadPool ASAP, without deferring, but we still enqueue/defer the writeToMessageQueue part. This way we can make full use of thread pool for computation, but still ensure the sequential writing order to MQ.
I rest my case.
This question already has answers here:
How do I convert an existing callback API to promises?
(24 answers)
Closed 3 years ago.
I have done one project where I used one callback so far, but with a bigger code base I already have chaos in it. I would like to move on to something new, specifically on async functions.
I'm using an oop pattern, so I'd like this to be implemented right into the oop.
The problem is that, I've never done this before and I don't know how to do it. I have some basic code with a callback. Could anyone change this to an async function, please?
server.js
const object = require("./object");
new object(userID).name((data) => {
console.log(data);
});
object.js
module.exports = class{
constructor(userID){
this.id = userID;
}
name(callback){
mysqli.query("SELECT meno FROM uzivatelia WHERE id='"+ this.id +"'", (err, user) => {
callback(user[0].meno);
});
}
}
What #Tom suggested is correct. Another option is to use node's util.promisify, which achieves the same thing but is a little terser.
const util = require('util');
const mysqliQueryP = util.promisify(mysqli.query);
module.exports = class{
constructor(userID){
this.id = userID;
}
name() {
return mysqliQueryP("SELECT meno FROM uzivatelia WHERE id='"+ this.id +"'");
}
}
Return an explicit Promise that you wire into the mysqli callback pattern:
module.exports = class {
constructor(userID) {
this.id = userID;
}
name() {
return new Promise((resolve, reject) => {
mysqli.query(
`SELECT meno FROM uzivatelia WHERE id = '${this.id}'`,
(error, record) => error ? reject(error) : resolve(record)
)
})
}
}
You could also add async before the method definition, but I think that won't have any impact -- the method becomes asynchronous when you return a Promise whether or not you declare it using async, and calling code will be able to await it.
const User = require('./user.js')
async testIt() {
const myUser = new User(12345)
const username = await myUser.name()
console.log(username)
}
testIt()
A couple of general tips. First, to make stacktraces clear, you're going to want to name this class.
Second, it might be worth your while to investigate a solution for de-node-ifying your mysqli calls across the board. I would be very surprised if something like that doesn't already exist. If it doesn't, you could pretty easily build something small that covers the cases you need most.
Currently, I am trying to get the md5 of every value in array. Essentially, I loop over every value and then hash it, as such.
var crypto = require('crypto');
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
for (var userID in watching) {
refPromises.push(admin.database().ref('notifications/'+ userID).once('value', (snapshot) => {
if (snapshot.exists()) {
const userHashString = userHash(userID)
console.log(userHashString.toUpperCase() + "this is the hashed string")
if (userHashString.toUpperCase() === poster){
return console.log("this is the poster")
}
else {
..
}
}
else {
return null
}
})
)}
However, this leads to two problems. The first is that I am receiving the error warning "Don't make functions within a loop". The second problem is that the hashes are all returning the same. Even though every userID is unique, the userHashString is printing out the same value for every user in the console log, as if it is just using the first userID, getting the hash for it, and then printing it out every time.
Update LATEST :
exports.sendNotificationForPost = functions.firestore
.document('posts/{posts}').onCreate((snap, context) => {
const value = snap.data()
const watching = value.watchedBy
const poster = value.poster
const postContentNotification = value.post
const refPromises = []
var crypto = require('crypto');
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
for (let userID in watching) {
refPromises.push(admin.database().ref('notifications/'+ userID).once('value', (snapshot) => {
if (snapshot.exists()) {
const userHashString = userHash(userID)
if (userHashString.toUpperCase() === poster){
return null
}
else {
const payload = {
notification: {
title: "Someone posted something!",
body: postContentNotification,
sound: 'default'
}
};
return admin.messaging().sendToDevice(snapshot.val(), payload)
}
}
else {
return null
}
})
)}
return Promise.all(refPromises);
});
You have a couple issues going on here. First, you have a non-blocking asynchronous operation inside a loop. You need to fully understand what that means. Your loop runs to completion starting a bunch of non-blocking, asynchronous operations. Then, when the loop finished, one by one your asynchronous operations finish. That is why your loop variable userID is sitting on the wrong value. It's on the terminal value when all your async callbacks get called.
You can see a discussion of the loop variable issue here with several options for addressing that:
Asynchronous Process inside a javascript for loop
Second, you also need a way to know when all your asynchronous operations are done. It's kind of like you sent off 20 carrier pigeons with no idea when they will all bring you back some message (in any random order), so you need a way to know when all of them have come back.
To know when all your async operations are done, there are a bunch of different approaches. The "modern design" and the future of the Javascript language would be to use promises to represent your asynchronous operations and to use Promise.all() to track them, keep the results in order, notify you when they are all done and propagate any error that might occur.
Here's a cleaned-up version of your code:
const crypto = require('crypto');
exports.sendNotificationForPost = functions.firestore.document('posts/{posts}').onCreate((snap, context) => {
const value = snap.data();
const watching = value.watchedBy;
const poster = value.poster;
const postContentNotification = value.post;
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
return Promise.all(Object.keys(watching).map(userID => {
return admin.database().ref('notifications/' + userID).once('value').then(snapshot => {
if (snapshot.exists()) {
const userHashString = userHash(userID);
if (userHashString.toUpperCase() === poster) {
// user is same as poster, don't send to them
return {response: null, user: userID, poster: true};
} else {
const payload = {
notification: {
title: "Someone posted something!",
body: postContentNotification,
sound: 'default'
}
};
return admin.messaging().sendToDevice(snapshot.val(), payload).then(response => {
return {response, user: userID};
}).catch(err => {
console.log("err in sendToDevice", err);
// if you want further processing to stop if there's a sendToDevice error, then
// uncomment the throw err line and remove the lines after it.
// Otherwise, the error is logged and returned, but then ignored
// so other processing continues
// throw err
// when return value is an object with err property, caller can see
// that that particular sendToDevice failed, can see the userID and the error
return {err, user: userID};
});
}
} else {
return {response: null, user: userID};
}
});
}));
});
Changes:
Move require() out of the loop. No reason to call it multiple times.
Use .map() to collect the array of promises for Promise.all().
Use Object.keys() to get an array of userIDs from the object keys so we can then use .map() on it.
Use .then() with .once().
Log sendToDevice() error.
Use Promise.all() to track when all the promises are done
Make sure all promise return paths return an object with some common properties so the caller can get a full look at what happened for each user
These are not two problems: the warning you get is trying to help you solve the second problem you noticed.
And the problem is: in Javascript, only functions create separate scopes - every function you define inside a loop - uses the same scope. And that means they don't get their own copies of the relevant loop variables, they share a single reference (which, by the time the first promise is resolved, will be equal to the last element of the array).
Just replace for with .forEach.
I'm trying to send a notification with Firebase Cloud Functions when someone likes a photo. I've coped the Firebase example showing how to send one when someone follows you and tried to modify it.
The problem is that I need to do another additional query in the function to get the key of the person who liked a photo before I can get their token from their User node. The problem is that the getDeviceTokensPromise somehow is not fulfilled and tokensSnapshot.hasChildren can't be read because it is undefined. How can I fix it?
exports.sendPhotoLikeNotification = functions.database.ref(`/photo_like_clusters/{photoID}/{likedUserID}`)
.onWrite((change, context) => {
const photoID = context.params.photoID;
const likedUserID = context.params.likedUserID;
// If un-follow we exit the function
if (!change.after.val()) {
return console.log('User ', likedUserID, 'un-liked photo', photoID);
}
var tripName;
var username = change.after.val()
// The snapshot to the user's tokens.
let tokensSnapshot;
// The array containing all the user's tokens.
let tokens;
const photoInfoPromise = admin.database().ref(`/photos/${photoID}`).once('value')
.then(dataSnapshot => {
tripName = dataSnapshot.val().tripName;
key = dataSnapshot.val().ownerKey;
console.log("the key is", key)
return getDeviceTokensPromise = admin.database()
.ref(`/users/${key}/notificationTokens`).once('value');
///// I need to make sure that this returns the promise used in Promise.all()/////
});
return Promise.all([getDeviceTokensPromise, photoInfoPromise]).then(results => {
console.log(results)
tokensSnapshot = results[0];
// Check if there are any device tokens.
//////This is where I get the error that tokensSnapshot is undefined///////
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
// Notification details.
const payload = {
notification: {
title: 'Someone liked a photo!',
body: `${username} liked one of your photos in ${tripName}.`,
}
};
// Listing all tokens as an array.
tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload);
});
Ok so based on your comment, here's the updated answer.
You need to chain your promises and you don't need to use Promise.all() - that function is used for a collection of asynchronous tasks that all need to complete (in parallel) before you take your next action.
Your solution should look like this:
admin.database().ref(`/photos/${photoID}`).once('value')
.then(dataSnapshot => {
// Extract your data here
const key = dataSnapshot.val().ownerKey;
// Do your other stuff here
// Now perform your next query
return admin.database().ref(`/users/${key}/notificationTokens`).once('value');
})
.then(tokenSnapshot => {
// Here you can use token Snapshot
})
.catch(error => {
console.log(error);
});
Because your second network request depends on the completion of the first, chain your promises by appending a second .then() after your first and don't terminate it with a semi-colon ;. If the new promise created within first .then() scope resolves it will call the second .then() function.
If you need to access variables from the first .then() scope in the second, then you must declare them in the general function, and assign them in the closure scopes.
You can check out more info on promise chaining here.
This is more of a conceptual question as logically, I can reduce the code below. Per the comments, user information is extracted and put in props. The same user data was then pulled from the database to be returned using done.
I think the original author of the code wanted to make sure it was saved to the database. But I think that is overkill.
Error checking will let us know if anything went wrong and we don't need to pull the data immediately after saving it. We can just return the same data that was stored.
This is passport authentication code.
// the user was not found
// create the user, get the user, and return the user object
function createUser (done, profile) {
let props = obtainProps(profile);
DBM.createUser(props).then(() => {
DBM.getUser(props.id_google).then((res) => {
return done(null, res[0]);
}).catch( error => {
return done(error, null);
});
});
}
to this code:
// the user was not found
// create the user then return props
function createUser (done, profile) {
let props = obtainProps(profile);
DBM.createUser(props).then(() => {
return done(null, props);
}).catch( error => {
return done(error, null);
});
}
I think that you can omit the check too, in addition, as commented, it would make sense to use the Promise and return that, instead of passing a callback and execute it in the promise's then. Promises were made to replace callbacks, your code is going in the other direction. Your createUser function should look like
function createUser (profile) {
let props = obtainProps(profile);
return DBM.createUser(props);
}
and if you need the result somewhere else, chain the function with then
// somewhere else in your code
createUser().then(e => /* e is result of DBM.createUser */)