As firebase functions now run w Node8, I would like to transform my current ES5 function w Promise flow to ES6 async/await
my flow pattern is the following :
const AUTHORIZED = authorizedApi()
if AUTHORIZED
const SENT = sendContactMessage())
if SENT
const FOUND = findContact(
if FOUND
return "FINISHED"
if !FOUND
const CREATED = createContact()
if CREATED
return "FINISHED"
Currently I am using a specific conditionalPromiseFlow() function as following : ( need to handle also the errors..
const conditionalPromiseFlow = (...fns) => {
if (fns.length === 0) return Promise.resolve();
const [next] = fns;
return next().then(result => {
if (result) {
return conditionalPromiseFlow(...fns.slice(1));
}
return result;
});
};
and I call it :
conditionalPromiseFlow(
() => authorizedApi(jwtClient),
() => sendContactMessage(gmailAPI, encodedContactMessage),
() =>
findContact(
googlePeopleAPI.connections,
googleConnectionListParams,
sender.email
),
() => createContact(googlePeopleAPI, googlePeopleContactParams)
)
.then(
res => {
return { status: 200, infos: "done" };
},
error => {
return { status: error.status, infos: error.message };
}
)
.then(response => {
return res.send(response);
})
.catch(console.error);
this runs well, but I guess that async/await pattern would simplify my code... Is it true or should I stick to my current code ?
thanks for feedback
Assuming this is not contained in an async function, the async/await equivalent would be:
(async() => {
try {
await authorizedApi(jwtClient);
await sendContactMessage(gmailAPI, encodedContactMessage);
await findContact(
googlePeopleAPI.connections,
googleConnectionListParams,
sender.email
);
await createContact(googlePeopleAPI, googlePeopleContactParams);
res.send({ status: 200, infos: "done" });
} catch (error) {
res.send({ status: error.status, infos: error.message });
}
))();
Whether that's simpler and a change worth making is obviously up to you.
(From your code, I take it when the promises returned by those functions reject, the object they provide has a status on it.)
Note that I didn't put a try/catch around the last res.send. I don't think it throws, but you did have a catch handler on it. So if it throws, you'd want to put that back.
If you're already in an async function, obviously you don't need that async wrapper:
try {
await authorizedApi(jwtClient);
await sendContactMessage(gmailAPI, encodedContactMessage);
await findContact(
googlePeopleAPI.connections,
googleConnectionListParams,
sender.email
);
await createContact(googlePeopleAPI, googlePeopleContactParams);
res.send({ status: 200, infos: "done" });
} catch (error) {
res.send({ status: error.status, infos: error.message });
}
By res.send it seems like You're using express framework - so You can make handler to be async wrapper, it's enough to put async word before (req, res):
app.get('/something', async (req, res) => {
try {
/*
await stuff here
*/
res.send({ status: 200, infos: "done" });
} catch (error) {
res.send({ status: error.status, infos: error.message });
}
});
Note that both in the above and in the async wrapper in the first code block that the entire body is in the try (other than the res.send on error). That's because nothing will handle the promise from the async function (Express doesn't do anything with the return value of route callbacks), so it's important that promise doesn't reject.
Related
I've been trying to create a helper function to return a document's data from the Firebase database using Nodejs:
module.exports = async (collectionName, documentId, res) => {
const collection = db.doc(`/${collectionName}/${documentId}`);
try {
const targetedDocument = await collection.get();
if (!targetedDocument.exists) {
return res.status(404).json({
error: true,
message: `the document ${documentId} is not exists.`,
});
}
return targetedDocument.data();
} catch (error) {
return res.status(500).json({ error: true, message: error });
}
};
But when I tried to use it, it always returns back a promise:
const documentFinder = require('./helper-function-path');
router.post('post',(req,res)=>{
const requiredDocument = documentFinder("post", "Izkn12IMnayzokLqe",res);
console.log(requiredDocument); //returned a promise rather than an object document
})
What am I doing wrong here? Some pointer would be very much appreciated. Thank you.
async functions, by definition, always return a promise. You can't make an asynchronous function to be synchronous simply by wrapping it. The caller will always still have the promise to deal with. You can deal with the returned promise in your express route by making its callback also async, and awaiting the result of the function call:
router.post('post', async (req,res)=>{
const requiredDocument = await documentFinder("post", "Izkn12IMnayzokLqe",res);
console.log(requiredDocument);
})
Please try out:
module.exports = async (collectionName, documentId, res) => {
const collection = db.doc(`/${collectionName}/${documentId}`);
try {
const targetedDocument = await collection.get();
if (!targetedDocument.exists) {
return res.status(404).json({
error: true,
message: `the document ${documentId} is not exists.`,
});
} else {
return targetedDocument.data();
}
} catch (error) {
return res.status(500).json({ error: true, message: error });
}
};
let { errors } = otherValdations(data);
withDB(async (db) => {
return Promise.all([
..code...
]).then(() => {
return {
errors,
isValid: isEmpty(errors),
}
})
}, res).then((result) => {
console.log(result);
})
How can I get 'result' variable to be the value of the object returned in promise.all? This is the code for withDB function:
const withDB = async (operations, res) => {
try {
const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
const db = client.db('app');
await operations(db);
client.close();
} catch (error) {
res.status(500).json({ message: 'Error connecting to db', error});
}
};
You need to modify withDB() so that it returns the value you want:
const withDB = async (operations, res) => {
try {
const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
const db = client.db('app');
let result = await operations(db);
client.close();
return result;
} catch (error) {
res.status(500).json({ message: 'Error connecting to db', error});
throw error;
}
}
In your catch() handler, you also need to do something so that your calling code can distinguish the error path where you've already sent an error response from the case where you're resolved with the value. I don't know exactly how you want that to work, but I put in a throw error so that it will reject the returned promise and the caller can see that.
I notice from your error handling that you are assuming all possible errors are causing by an error connecting to the DB. That is not the case here. If operations(db) rejects, that will also hit your catch.
Promise.all returns an array with the results. So you either have to loop over the results or access them directly by supplying an index.
i have an async function that do not work as expected, here is the code :
const onCreateCoachSession = async (event, context) => {
const { coachSessionID } = context.params;
let coachSession = event.val();
let opentokSessionId = 'prout';
await opentok.createSession({ mediaMode: 'relayed' }, function(
error,
session
) {
if (error) {
console.log('Error creating session:', error);
} else {
opentokSessionId = session.sessionId;
console.log('opentokSessionIdBefore: ', opentokSessionId);
const sessionId = session.sessionId;
console.log('Session ID: ' + sessionId);
coachSession.tokbox = {
archiving: true,
sessionID: sessionId,
sessionIsCreated: true,
};
db.ref(`coachSessions/${coachSessionID}`).update(coachSession);
}
});
console.log('opentokSessionIdEnd: ', opentokSessionId);
};
My function onCreateCoachSession trigger on a firebase event (it's a cloud function), but it does not end for opentok.createSession to end, i don't understand why as i put an await before.
Can anyone have an idea why my code trigger directly the last console log (opentokSessionIdEnd)
Here is a screenshot on order of console.log :
It's probably a simple problem of async/await that i missed but i cannot see what.
I thanks in advance the community for the help.
You're using createSession in callback mode (you're giving it a callback function), so it doesn't return a Promise, so it can't be awaited.
Two solutions :
1/ Use createSession in Promise mode (if it allows this, see the doc)
let session = null;
try{
session = await opentok.createSession({ mediaMode: 'relayed' })
} catch(err) {
console.log('Error creating session:', error);
}
or 2/ await a Promise
let session;
try {
session = await new Promise((resolve, reject) => {
opentok.createSession({ mediaMode: 'relayed' }, (error, session) => {
if (error) {
return reject(error)
}
resolve(session);
})
})
} catch (err) {
console.log('Error creating session:', err);
throw new Error(err);
}
opentokSessionId = session.sessionId;
console.log('opentokSessionIdBefore: ', opentokSessionId);
// ...
await means it will wait till the promise is resolved. I guess there is no promise returned in this case. you can create your own promise and handle the case
So, I'm new trying to understand how async functions work. Doing it with "Resolve, Reject" from Promises, works fine. But when I try to apply it with async instead of new Promise, for some reason, the function returns undefined. Here is my code :)
note: Sorry for my english, not fluent speaker :)
category = body.category
obtenerCategoria = async(category) => {
Categoria.findOne({ descripcion: category })
.exec((err, categoriaDB) => {
if (err) {
throw new Error(err)
}
if (!categoriaDB) {
return res.status(400).json({
ok: false,
msg: 'Categoria no encontrada'
})
}
console.log(categoriaDB); // Works fine here
return categoriaDB
})
}
crearClase = async() => {
categoria = await obtenerCategoria(category);
console.log(categoria); //getting nothing here
}
crearClase()
.then()
.catch(e => {
return e
})
You don't need to use callback function when you use async/await
Try this code:
obtenerCategoria = async(category) => {
const categoriaDB = await Categoria.findOne({ descripcion: category });
if (!categoriaDB) {
return res.status(400).json({
ok: false,
msg: 'Categoria no encontrada'
})
}
console.log(categoriaDB); // Works fine here
return categoriaDB
}
obtenerCategoria doesn't have a return value. Additionally, you can use await on the Categoria.findOne() call and use try / catch instead of the call to exec(), something like this example.
This is the code for a google cloud function I am trying to deploy. I'm getting a error saying that my .then() promises or inconsistent. Does anyone know what I am doing wrong?
const admin = require('firebase-admin');
const twilio = require('./twilio');
module.exports = function(req, res) {
if (!req.body.phone) {
return res
.status(422)
.send({ error: 'You must provide a phone number' });
}
const phone = String(req.body.phone).replace(/[^\d]/g, '');
admin
.auth()
.getUser(phone)
.then(userRecord => {
const code = Math.floor((Math.random() * 8999 + 1000));
const message = {
body: 'Your code is ' + code,
to: phone,
from: '+18053167032'
};
const callback = (err) => {
if (err) {
return res.status(422).send(err);
}
admin
.database()
.ref('users/' + phone)
.update(
{ code: code, codeValid: true },
() => { res.send({ success: true }
);
};
twilio.messages.create(message, callback);
})
.catch((err) => {
res.status(422).send({ error: err });
});
}
Off the top of my head, it is hard your indentation blocks using the curly braces exactly, and in response to #hanoldaa's mention of arrow functions, it is quite important to be able to trace exactly where the userRecord => function will end. If it says your .then promises are inconsistent, then I would assume you are either calling .then on non-promise objects, or you are not handling unresolved Promises.
Javascript.info has a great suggestion on a global handling of unresolved promises, using:
window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // [object Promise] - the promise that generated the error
alert(event.reason); // Error: Whoops! - the unhandled error object
});
new Promise(function() {
throw new Error("Whoops!");
}); // no catch to handle the error
Hope this helps!
At the end, you do
.catch((err) => {
res.status(422).send({ error: err });
});
but err shouldn't be wrapped in parenthesis. Use
.catch(err => {
res.status(422).send({ error: err });
});