This one is starting to get under my skin...
I'm playing around with Firebase and Functions right now and made a very simple API with express as the middleware.
So I have this route:
...
app.get('/getAuthUrl', async (req, res) => {
const s: sessionManagement.ISessionManagement =
sessionManagement.SessionFactory.createSession(
functions.config().aproxy.session.mode, req, db)
// ...this work
const getback = req.query.getback;
await db.collection('tokens').doc('getback').set({getback});
// this not?!
s.setReturnURL(req.query.getback);
res.setHeader('Cache-Control', 'private');
res.status(200).json(new Message(redirectUri));
})
At first, I use the 2 lines under the comment "...this work" and it did work; writing an URL to a Firestore database. So far, so good.
Then I decided to get fancy and decide to use a object factory to manage the fact that using Express session on a localhost serving functions (with the emulator) on one port and the angular frontend on an other was causing some headache with session management. Long story short, I setup a factory that will return me a object that will manage if I was running localy or on Firebase cloud hosting and used a different strategy.
This is where Promise started to not holding their... promise!?
The line s.setReturnURL(.. call this method inside my factory:
export interface ISessionManagement {
setReturnURL: (value: string) => void
}
export class FirestoreSession implements ISessionManagement {
private database: FirebaseFirestore.Firestore
private fingerprint: string
constructor(database: FirebaseFirestore.Firestore, fingerprint: string) {
this.database = database
this.fingerprint = fingerprint
}
setReturnURL(value: string) {
console.log('setReturnURL')
this.writeDoc(value, 'getback')
}
writeDoc(value: string, document: string) {
(async () => {
console.log('inside async')
const doc = this.database.collection('tokens').doc(document).set({value})
const result = await doc
console.log(result)
})().catch(error => {
console.log(error)
})
console.log('finish writeDoc')
}
}
export class SessionFactory {
public static createSession(mode: string, req: express.Request, db: FirebaseFirestore.Firestore) : ISessionManagement {
if (mode === 'fingerprint' ) {
console.log('fingerprint mode');
// Use for local testing, since in multiport solution, cookie will not be unique to a session
return new FirestoreSession(db, fingerPrintMe(req));
} else {
// For production
return new ExpressSession(req);
}
}
}
So, here the console output of the flow of execution if a use my factory object:
✔ functions[app]: http function initialized (http://localhost:5000/myplayground/us-central1/app).
i functions: Beginning execution of "app"
> fingerprint mode
> setReturnURL
> inside async
> finish writeDoc
i functions: Finished "app" in ~1s
The code above is my latest attempt to make this work. I tried A LOT of variation, moving my async/await around but nothing budge!
What am I missing??? I feel the answer will make me crawl under some rock but I don't care, I need to take this one off my mind :-)
UPDATE
Turns out that I need to async/await all the way down to the call that return a promise to make this work AND ALSO, you cannot await a function that return void, so I had to change the interface signature. So here the code fragment that work :
app.get('/getAuthUrl', async (req, res) => {
...
await s.setReturnURL(req.query.getback)
...
})
...
export interface ISessionManagement {
setReturnURL: (value: string) => any
}
export class FirestoreSession implements ISessionManagement {
...
async setReturnURL(value: string) {
await this.writeDoc(value, 'getback')
}
...
async writeDoc(value: string, document: string) {
try {
const doc = await this.database.collection('tokens').doc(document).set({value})
console.log(doc)
} catch (error) {
console.log(error)
}
}
}
const doc = this.database.collection('tokens').doc(document).set({value})
const result = await doc
Why not just const result = await this.database.collection('tokens').doc(document).set({value})?
`writeDoc(value: string, document: string) {
(async () => {
console.log('inside async')
const doc = this.database.collection('tokens').doc(document).set({value})
const result = await doc
console.log(result)
})().catch(error => {
console.log(error)
})
console.log('finish writeDoc')
}`
First, writedoc is not a promise and isn't awaited. The internal async function isn't awaited either, so once execution pauses the rest of the function moves on. So it will execute and the rest of internal async function will be queued for the next event loop. Then everything returns and exits.
Make writedoc async, and await it and the internal async function.
Related
I have this function which calls another microservice function and I want to return the tags after all elements are added in the subscribe.
This is done in nestjs using microservices.
As it is right now this just returns the empty array, but I want it to return it with the elements.
Does anyone know a fix? thansk
private microservicesOptions: ClientOptions = {
transport: Transport.TCP,
options: {
host: host,
port: 3006
}
}
private filterProxy: ClientProxy;
constructor() {
this.filterProxy = ClientProxyFactory.create(this.microservicesOptions);
}
async getAllTags() {
let tags = []
this.postMicroserviceProxy.send<any>("get_posts", "").subscribe(response => {
response.forEach(element => {
element.tags.forEach(tag => {
tags.push(tag)
})
});
});
return tags;
}
You don't need an async method. async only makes sense when you also use await, and await is not necessary here.
Just return a regular promise:
getAllTags() {
return this.postMicroserviceProxy
.send<any>("get_posts", "")
.toPromise()
.then(response => response.flatMap(element => element.tags));
}
Because it returns a promise, you can still await this function, though:
async test() {
const tags = await foo.getAllTags();
}
I'm new to fetching and posting data using an API, and I can't work out how to do something once my Post has been completed.
I have a function that calls the API with the Post data. I need to set the loading state to false once the Post has been completed. Everything works apart from that, the data gets sent to Mongo, I just need to turn off my loading spinner once it has completed.
How do I do this, please?
This is how I'm trying to do it:
const postData = async () => {
setLoading(true)
await axios.post('/api/addData',form)
.then(response => {
setLoading(false)
})
}
And this is the API bit:
import { connectToDatabase } from "util/mongodb"
export default async (req, res) => {
const { db } = await connectToDatabase()
await db
.collection("posts")
.insertOne(req.body);
}
There is two potential problem in your code, first you're not sending any data back to the front in your backend code. Usually you send back the id of the inserted element (It can be usefull to do some mutation in your front), you'll also need to try catch your call to the db to notify that something went wrong to the front end side :
import { connectToDatabase } from "util/mongodb"
export default async (req, res) => {
try {
const { db } = await connectToDatabase()
const insertedPost = await db
.collection("posts")
.insertOne(req.body);
res.status(201).send(insertedPost.insertedId);
// again it's up to you to know what can be usefull to your front-end to use
// Look at http status code online to know what's the best fit
} catch (err) {
res.status(500).send(err.message);
// send whatever that can be usefull for your front end to handle the error
}
}
In your front-end code you're using await with .then, it's weird usage. You can put your setLoading(false) after the await without the .then but you'll still need to try catch it. What I prefer to do is using the finally block to stop loading, so if my api call fail the loading is still stopped :
const postData = async () => {
setLoading(true)
try {
const response = await axios.post('/api/addData',form)
// do something with response
} catch (err) {
// notify user that something went wrong
} finally {
setLoading(false);
}
}
const postData = () => {
setLoading(true)
axios.post('/api/addData',form)
.then(response => {
// do something with response
})
.catch((err) => {
// notify user that something went wrong
})
.finally(() => {
setLoading(false);
})
}
Building a NodeJS REST API.
Trying to send load data from FireBase collection, then sending it to the user (as API response).
Looks like the problem is that it's not waits for the firebase fetch to resolve, but send back a response without the collection data. (tried to use ASYNC-AWAIT but its not working)
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {
snapshot.docs.forEach(msg => {
console.log(msg.data().messageContent)
return {
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}
})
})
}
try {
const chatData = await getChatData()
console.log(chatData)
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData
})
} catch (err) {
if (!err.statusCode) {
err.statusCode(500)
}
next(err)
}
}
As you can see, I've used 2 console.logs to realize what the problem, Terminal logs looks like:
[] (from console.logs(chatData))
All messages (from console.log(msg.data().messageContent))
Is there any way to block the code unti the firebase data realy fetched?
If I correctly understand, you want to send back an array of all the documents present in the messages subcollection. The following should do the trick.
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId;
const collectionRef = db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc');
try {
const chatsQuerySnapshot = await collectionRef.get();
const chatData = [];
chatsQuerySnapshot.forEach((msg) => {
console.log(msg.data().messageContent);
chatData.push({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
});
});
console.log(chatData);
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData,
});
} catch (err) {
if (!err.statusCode) {
err.statusCode(500);
}
next(err);
}
};
The asynchronous get() method returns a QuerySnapshot on which you can call forEach() for enumerating all of the documents in the QuerySnapshot.
You can only await a Promise. Currently, getChatData() does not return a Promise, so awaiting it is pointless. You are trying to await a fixed value, so it resolves immediately and jumps to the next line. console.log(chatData) happens. Then, later, your (snapshot) => callback happens, but too late.
const getChatData = () => new Promise(resolve => { // Return a Promise, so it can be awaited
db.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot(resolve) // Equivalent to .onSnapshot((snapshot) => resolve(snapshot))
})
const snapshot = await getChatData();
console.log(snapshot)
// Put your transform logic out of the function that calls the DB. A function should only do one thing if possible : call or transform, not both.
const chatData = snapshot.map(msg => ({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}));
res.status(200).json({
message: 'Chat Has Found',
chatData
})
Right now, getChatData is this (short version):
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {}) // some things inside
}
What that means is that the getChatData function calls some db query, and then returns void (nothing). I bet you'd want to return the db call (hopefully it's a Promise), so that your await does some work for you. Something along the lines of:
const getChatData = async () =>
db
.collection('chats')
// ...
Which is the same as const getChatData = async() => { return db... }
Update: Now that I've reviewed the docs once again, I see that you use onSnapshot, which is meant for updates and can fire multiple times. The first call actually makes a request, but then continues to listen on those updates. Since that seems like a regular request-response, and you want it to happen only once - use .get() docs instead of .onSnapshot(). Otherwise those listeners would stay there and cause troubles. .get() returns a Promise, so the sample fix that I've mentioned above would work perfectly and you don't need to change other pieces of the code.
I am wondering how to properly test Azure Functions with Jest. I have read the online documentation provided by MSoft but it's very vague, and brief. There are also some outdated articles I found that don't really explain much. Here is what I understand: I understand how to test normal JS async functions with Jest. And I understand how to test very simple Azure Functions. However I am not sure how to go about properly testing more complex Azure Functions that make multiple API calls, etc.
For example I have an HTTP Function that is supposed to make a few API calls and mutate the data and then return the output. How do I properly mock the API calls in the test? We only have one point of entry for the function. (Meaning one function that is exported module.exports = async function(context,req). So all of our tests enter through there. If I have sub functions making calls I can't access them from the test. So is there some clever way of mocking the API calls? (since actually calling API's during tests is bad practice/design)
Here is a sample of code to show what I mean
module.exports = async function (context, req)
{
let response = {}
if (req.body && req.body.id)
{
try
{
//get order details
response = await getOrder(context, req)
}
catch (err)
{
response = await catchError(context, err);
}
}
else
{
response.status = 400
response.message = 'Missing Payload'
}
//respond
context.res =
{
headers: { 'Content-Type': 'application/json' },
status: response.status,
body: response
}
};
async function getOrder(context, req)
{
//connection to db
let db = await getDb() // <- how to mock this
//retrieve resource
let item = await db.get...(id:req.body.id)... // <- and this
//return
return {'status':200, 'data':item}
}
Consider this (simplified) example.
src/index.js (Azure Function entry point):
const { getInstance } = require('./db')
module.exports = async function (context) {
// assuming we want to mock getInstance and db.getOrder
const db = await getInstance()
const order = await db.getOrder()
return order
}
src/db.js:
let db
async function getInstance() {
if (db === undefined) {
// connect ...
db = new Database()
}
return db
}
class Database {
async getOrder() {
return 'result from real call'
}
}
module.exports = {
getInstance,
Database,
}
src/__tests__/index.test.js:
const handler = require('../index')
const db = require('../db')
jest.mock('../db')
describe('azure function handler', () => {
it('should call mocked getOrder', async () => {
const dbInstanceMock = new db.Database() // db.Database is already auto-mocked
dbInstanceMock.getOrder.mockResolvedValue('result from mock call')
db.getInstance.mockResolvedValue(dbInstanceMock)
const fakeAzureContext = {} // fake the context accordingly so that it triggers "getOrder" in the handler
const res = await handler(fakeAzureContext)
expect(db.getInstance).toHaveBeenCalledTimes(1)
expect(dbInstanceMock.getOrder).toHaveBeenCalledTimes(1)
expect(res).toEqual('result from mock call')
})
})
> jest --runInBand --verbose
PASS src/__tests__/index.test.js
azure function handler
✓ should call mocked getOrder (4 ms)
For a complete quickstart, you may want to check my blog post
The problem I'm having is that Jest is reporting setResultsSpy is being called 0 times when in fact, I know that it is being called. I know this by putting console.log(results) under the const results = await getFileList(data.path); in my code and was able to see results returned.
My guess right now is that try-catch blocks creates a local scope, which is causing those calls to not be registered. If this is true, my question is "how can I test if those methods have been called"?
// test_myFunction.js
test((`myFunction with valid path should return list of files`), () => {
const actions = {
setMsg: () => { },
setButton: () => {},
setResults: () => {},
setAppState: () => {}
};
const setMsgSpy = jest.spyOn(actions, 'setMsg');
const setSubmitButtonStateSpy = jest.spyOn(actions, 'setButton');
const setResultsSpy = jest.spyOn(actions, 'setResults');
const setAppStateSpy = jest.spyOn(actions, 'setAppState');
const returnedFileList = [
'file1.pdf',
'file2.pdf',
'file3.pdf',
];
const requestConfig = {
component: COMPONENTS.myComponent,
request: RequestTypes.REQUEST,
data: {path: 'folder1'},
actions
};
processRequest(requestConfig)
expect(setMsgSpy).toHaveBeenCalledTimes(1);
expect(setMsgSpy)
.toHaveBeenCalledWith('loading');
expect(setButtonSpy).toHaveBeenCalledTimes(1);
expect(setResultsSpy).toHaveBeenCalledTimes(1);
expect(setResultsSpy).toHaveBeenCalledWith(returnedFileList);
expect(setAppStateSpy).toHaveBeenCalledTimes(1);
expect(setAppStateSpy).toHaveBeenCalledWith('confirm');
});
_
// myFunction.js
async function processRequest({
component,
request,
data,
actions,
}){
if (component === COMPONENTS.myComponent) {
const path = data.path.trim();
switch (request) {
case RequestTypes.REQUEST:
actions.setMsg('message');
actions.setButton('disabled');
try {
const results = await getFileList(data.path);
actions.setResults(results);
actions.setAppState('confirm');
} catch (e) {
actions.setError(e);
actions.setAppState('error');
}
}
break;
default:
break;
}
}
The the problem was Jest was failing out of the test before the results from getFileList() execution has completed since getFileList() is an async function.
The solution is for the test to handle the execution asynchronously as per the documentation. There are 4 ways to solve this problem:
Use callbacks
Use .then() and .catch() on the returned promise (see docs on .then() here and .catch() here)
Use .resolves() or .rejects() Jest methods on expect() to let Jest resolve the promise.
Use Async-Await syntax by declaring the test anonymous function as async and using await on processRequest() .
I went with option 4 as I enjoy using async-await syntax. Here's the solution:
// test_myFunction.js
test((`myFunction with valid path should return list of files`), async () => {
//(all of the variables established from above)
await processRequest(requestConfig)
expect(setMsgSpy).toHaveBeenCalledTimes(1);
expect(setMsgSpy)
.toHaveBeenCalledWith('loading');
expect(setButtonSpy).toHaveBeenCalledTimes(1);
expect(setResultsSpy).toHaveBeenCalledTimes(1);
expect(setResultsSpy).toHaveBeenCalledWith(returnedFileList);
expect(setAppStateSpy).toHaveBeenCalledTimes(1);
expect(setAppStateSpy).toHaveBeenCalledWith('confirm');
});
Notice async being used on the first line and await when calling processRequest(requestConfig).