I am working on a GraphQL query where I am trying to find a unique model. However, nothing ever gets returned because the code kept carrying on before the query was finished, thus attempted to return a Promise when it expected a Model. The code looks as follows...
const findShift = async (date) => {
console.log("In mutation function")
const foundShift = await db.shift.findUnique({
where: {
date: date
}
})
return foundShift
}
const foundShift = findShift(date).then( resolved => {
console.log("printing resolved...")
console.log(resolved)
if (resolved.id != 'undefined'){
console.log({
id: resolved.id,
date: resolved.date,
allDevices: resolved.allDevices
})
return foundShift
}
else{
throw new Error("no shift of that date found!")
}
})
And the console.log statements make the console look as so...
In mutation function
Promise { <pending> }
prisma:info Starting a postgresql pool with 9 connections.
and ultimately the query just returns null. As you see, I tried using then and putting the mutation itself into an entirely different function just to circumvent these asynchronisity issues to no avail. Does anyone see a workaround?
First off, ALL async functions return a promise. The return value in the async function becomes the resolved value of that promise. So, the caller of an async function MUST use .then() or await to get the resolved value from the async function. There is no way to "circumvent" the asynchronicity like you are attempting. You can tame it to make it more usable, but you can't escape it. So, your async function returns a pending promise that will eventually resolve to whatever value you return inside your async function.
You can read more about how async functions work here in this other answer.
In trying to make a minimal, reproducible example of your code, I've reduced it to this where I've substituted an asynchronous simulation for the database call:
function delay(t, v) {
return new Promise(resolve => setTimeout(resolve, t, v));
}
// simulate asynchronous database operation
const db = {
shift: {
findUnique: function(data) {
return delay(100, { id: 123, date: Date.now(), allDevices: ["iPhone", "Galaxy", "Razr"] });
}
}
}
const findShift = async (date) => {
console.log("In mutation function")
const found = await db.shift.findUnique({
where: {
date: date
}
})
return found;
}
const date = Date.now();
const foundShift = findShift(date).then(resolved => {
console.log("printing resolved...")
console.log(resolved);
if (resolved.id != 'undefined') {
console.log({
id: resolved.id,
date: resolved.date,
allDevices: resolved.allDevices
})
return foundShift
} else {
throw new Error("no shift of that date found!")
}
});
When I run this in nodejs, I get this error:
[TypeError: Chaining cycle detected for promise #<Promise>]
And, the error is caused by this line of code:
return foundShift
You are attempting to return a promise that's already part of this promise chain from within the promise chain. That creates a circular dependency which is not allowed.
What you need to return there is whatever you want the resolved value of the parent promise to be. Since that looks like it's the object you construct right above it, I've modified the code to do that. This code can be run and foundShift is a promise that resolves to your object.
function delay(t, v) {
return new Promise(resolve => setTimeout(resolve, t, v));
}
// simulate asynchronous database operation
const db = {
shift: {
findUnique: function(data) {
return delay(100, { id: 123, date: Date.now(), allDevices: ["iPhone", "Galaxy", "Razr"] });
}
}
}
const findShift = async (date) => {
const found = await db.shift.findUnique({
where: {
date: date
}
})
return found;
}
const date = Date.now();
const foundShift = findShift(date).then(resolved => {
if (resolved.id != 'undefined') {
let result = {
id: resolved.id,
date: resolved.date,
allDevices: resolved.allDevices
};
return result;
} else {
throw new Error("no shift of that date found!")
}
});
// foundShift here is a promise
// to get it's value, you have to use .then() or await on it
foundShift.then(result => {
console.log("final result", result);
}).catch(e => {
console.log(e);
});
Here are a couple of rule about promises that might help:
All fn().then() or fn().catch() calls return a new promise that is chained to the one that fn() returned.
All async functions return a promise.
You cannot "circumvent" asynchronicity and somehow directly return an asynchronously retrieved value. You will have to use a callback, an event or return a promise (or some similar asynchronous mechanism) in order to communicate back to the caller an asynchronously retrieved value.
await can only be used inside an async function (or at the top level of an ESM module).
The first await in a function suspends execution of the async function and then immediately returns an unfulfilled promise to the caller. So, the await only affects the current function flow, not the caller's flow. The caller will still have to use .then() or await to get the value out of the promise that the async function returns.
Try as you might, there is no way around these rules (in Javascript as it currently runs in a browser or in nodejs).
Related
export const loginSuccess = createAsyncThunk(
"auth/loginSuccess",
async (user: User) => {
const res = await api
.post(
"/auth/loginSuccess",
{ user },
{
withCredentials: true,
}
)
.then((res: any) => {
setAxiosToken(res.data.token);
saveToken(res.data.token);
return { ...res.data.data, token: res.data.token };
});
return res;
}
);
There are 2 return statements at the end so I am confused about which return value the fulfilled reducer will get. The code is written by someone else that's why I want to understand it.
The second return statement is the one which will return from your function.
The first is actually returning from the then function of the promise that axios returns.
This is made a little bit confusing by using the same name for the res variable in the thunk function, and for the response variable that is passed on the the then function.
But what you will receive back is the object generated in this line of code:
{ ...res.data.data, token: res.data.token }
Where res.data.data is spread into a new object, and res.data.token is assigned to the token property of that object.
Trying to read a pushToken from a given user in the users collection (after an update operation on another collection) returns undefined
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite((snap, context) => {
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
db.collection('Classificados').doc(classificadoId)
.update({
aprovado: false
})
.then(r => {
getToken(idTo).then(token => {
// sendMsg...
})
}).catch(updateErr => {
console.log("updateErr: " + updateErr)
})
async function getToken(id) {
let response = "getTokenResponse"
console.log("id in getToken: " + id)
return db.collection('users').doc(id).get()
.then(user => {
console.log("user in getToken: " + user.data())
response = user.data().pushToken
})
.catch(e => {
console.log("error get userToken: " + e)
response = e
});
return response
}
return null
});
And this is from the FB console log:
-1:43:33.906 AM Function execution started
-1:43:36.799 AM Function execution took 2894 ms, finished with status: 'ok'
-1:43:43.797 AM id in getToken: Fm1RwJaVfmZoSgNEFHq4sbBgoEh1
-1:43:49.196 AM user in getToken: undefined
-1:43:49.196 AM error get userToken: TypeError: Cannot read property 'pushToken' of undefined
-1:43:49.196 AM returned token: undefined
And we can see in this screenshot from the db that the doc does exist:
Hope someone can point me to what I'm doing wrong here.
added screenshot of second example of #Renaud as deployed:
As Doug wrote in his comment, you need to "return a promise from the top level function that resolves when all the async work is complete". He also explains that very well in the official video series: https://firebase.google.com/docs/functions/video-series/ (in particular the 3 videos titled "Learn JavaScript Promises"). You should definitely watch them, highly recommended!
So, the following modifications to your code should work (untested):
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite(async (snap, context) => { // <- note the async keyword
try {
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
await db.collection('Classificados').doc(classificadoId)
.update({
aprovado: false
});
const userToSnapshot = await db.collection('users').doc(idTo).get();
const token = userToSnapshot.data().pushToken;
await sendMsg(token); // <- Here you should take extra care to correctly deal with the asynchronous character of the sendMsg operation
return null; // <-- This return is key, in order to indicate to the Cloud Function platform that all the asynchronous work is done
} catch (error) {
console.log(error);
return null;
}
});
Since you use an async function in your code, I've used the async/await syntax but we could very well write it by chaining the promises with the then() method, as shown below.
Also, I am not sure, in your case, that it adds any value to put the code that gets the token in a function (unless you want to call it from other Cloud Functions but then you should move it out of the addDenuncia Cloud Function). That's why it has been replaced by two lines of code within the main try block.
Version with chaining promises via the then() method
In this version we chain the different promises returned by the asynchronous methods with the then() method. Compared to the async/await version above, it shows very clearly what means "to return a promise from the top level function that resolves when all the asynchronous work is complete".
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite((snap, context) => { // <- no more async keyword
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
return db.collection('Classificados').doc(classificadoId) // <- we return a promise from the top level function
.update({
aprovado: false
})
.then(() => {
return db.collection('users').doc(idTo).get();
})
.then(userToSnapshot => {
if {!userToSnapshot.exists) {
throw new Error('No document for the idTo user');
}
const token = userToSnapshot.data().pushToken;
return sendMsg(token); // Again, here we make the assumption that sendMsg is an asynchronous function
})
.catch(error => {
console.log(error);
return null;
})
});
I have a cloud code from which I call an external function.
The cloud code response is null but the console displays the response
my cloud code ;
Parse.Cloud.define("testccadd", async request => {
try {
var ccaddrequest = {
conversationId: '123456789',
email: 'email#email.com',
};
externalFunction (ccaddrequest, function (err, result) {
console.log(result);
return result;
}) ;
} catch (e) {
console.log("Error");
}
});
console.log (result); shows the values from the external function, but the return result; returns null
how can I get the external function response as response of my cloud code function ?
The problem is that your externalFunction uses a callback to return its result. That is an asynchronous event, meaning that it happens after your cloud functions has been processed.
The cloud function will execute var ccaddrequest... and then call externalFunction but it won't "wait" for externalFunction to call the callback function if it contains asynchronous commands.
So you need to wrap the externalFunction in a Promise (see how to promisify callbacks) and then await the result of it.
Plus you need to return the result of the Promise, so in your code you need to add
Parse.Cloud.define("testccadd", async request => {
try {
var ccaddrequest = {
conversationId: '123456789',
email: 'email#email.com',
};
var result = await externalFunctionPromise(...);
return result;
} catch (e) {
console.log("Error");
}
});
Ok so I am newbie in Angularjs,one of my current task is to code a CRUD functionality and I used the Promise to handle it.
dao.updateEntityCharSpecUseRelSql = function (paramField) {
return new Promise(function (resolve, reject) {
.......
}).catch(err => { reject(err)});// **my PM says, catching error is wrong**
}
module.exports = dao;
First, I thought, catch block is just alright because I am getting it from the Promise object which returns the error if something goes wrong.
But my Pm says, in order to use this, promises should have .then() first.
Is it really a bad practice to use the catch() without then(). What he propose is that instead, I should create a try & catch block inside the Promise() something like
new Promise (function(resolve, reject){
try {
resolve(something)
}catch(err){
reject(err)
}
})
Please enlighten me for this.TIA
Let's do a step back.
When you create a new Promise() you are responsible to handle each success and failure case.
Who is calling your function (and getting your promise) is responsible to handle the response inside a then() callback, if the promise was successfully resolved, or a catch() callback, if the promise was rejected.
So, you don't need to catch your own promise.
Example:
function init() {
getAsyncValues()
.then(function (result) {
// it will do something based on his business logic
})
.catch(function (error) {
// it will do something based on his business logic
});
}
function getAsyncValues() {
return new Promise(function(resolve, reject) {
...
resolve(SOME_VALUES);
...
reject(SOME_ERRORS);
})
}
And if your "promise handler" need to call async task before resolving something, you can wait his resolution like:
const examplePromise = new Promise(fuction (resolve, reject) {
asyncTask() // executing an async task
.then(function(result) {
resolve(result);
})
.catch(function(error) {
reject(error)
});
})
in TypeScript, to resolve the promise, I use the await keyword.
but that keyword is only allowed to exist in the body of async functions, which return Promise<T>.
in that case, the function that calls this async function, will need to resolve the return value: Promise<T>, which means i'll again need the await keyword, and that function will have to be defined as async as well.
What am I missing?
You don't have to await, you can use then on the promise.
Here's the scenario you've described:
async function fn(): Promise<number> {
return new Promise<number>((resolve, reject) => {
setTimeout(resolve.bind(null, 2), 1500);
});
}
async function fn2(num: number) {
return await fn() * num;
}
function fn3(num: number) {
fn2(num).then(num => {
console.log(`returned number: ${num}`);
})
}
fn3(10);
(code in playground)
Which outputs this after 1500 milliseconds:
returned number: 20
Edit
Doing this:
var x = fn2(3).then(num => { return num * 2 });
console.log(x);
Will log something like:
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
To get the value you need to use then:
x.then(num => console.log(num));
You can not assign the result of an async operation into a variable without using await.