Feathersjs: how to send a response and after that trigger a function? - javascript

I had some code working fine for a REST endpoint in which a message was:
created in the database
stepA was processed
when stepA was ok, the response message was returned
stepB was processed.
This was the code:
// POST single message
app.post('/message', (req, res) => {
const url = req.body.properties.url
const image = req.body.properties.image
const extraField = req.body.properties.extraField
db.message.create({
url: url,
image: image,
})
.then(() => myProcess(extraField, 'stepA'))
.then(newMessage => res.json(newMessage))
.then(() => myProcess(extraField, 'stepB'))
})
Now I am trying to have the same using feathersjs, but I do not know how to do 2, 3, 4 exactly.
I have now an AFTER hook for the create method of the message service:
module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
return function processNewMessage (hook) {
const { extraField } = hook.data.properties
Promise.resolve(myProcess(extraField, 'stepA'))
.then( <<NO-IDEA>> ) // Send RESPONSE!!
.then(() => myProcess(extraField, 'stepB'))
return Promise.resolve(hook);
};
};
So my question boils down to: How can I send the response and subsequently trigger 'myProcess stepB' using feathersjs?
Althoug this is 'legacy', I think it might still be relevant.

It is answered in the FAQ of feathersjs!
How to do processing after sending the response to the user:
It depends on the promise that you return in your hook. Here's an example of a hook that sends an email, but doesn't wait for a success message.
function (hook) {
// Send an email by calling to the email service.
hook.app.service('emails').create({
to: 'user#email.com',
body: 'You are so great!'
});
// Send a message to some logging service.
hook.app.service('logging').create(hook.data);
// Return a resolved promise to immediately move to the next hook
// and not wait for the two previous promises to resolve.
return Promise.resolve(hook);
}

Related

Cancel old Promise.resolve if same method called multiple times with given timeframe

I am using Promise and axios in react to call POST api and fetch list of records.
Issue is when multiple API calls triggered then any one which response last is getting is updating state.
Where i want to use only last called API response.
Exp : call API 3 times with different postbody, in case first call response take time than 2nd & 3rd then callback is using response of 1st call to setstate, instead i want to forget 1 and second call and consider last call response only.
Following is example
Common File
const apiService = axios.create({
baseURL: 'https://example.com/api/,
});
function post(postData) {
return Promise.resolve(apiService.post('https://example.com/api/getuserlist', postData, {
headers: {'Authorization': `Bearer sdfsdfsdf-cvdfs`}
}));
}
Service File
static getUsers(postData) {
return post(postData);
}
React Component
function getUsersList = (Filters )=>{
getUsers({ "Filters": Filters }).then((response) => {
this.setState({ users: response.data})
})
}
Problem is when getUsersList get called multiple time whichever is last response is getting set to users state, where last call should be users list.
It's not yet possible to actually cancel promises in JavaScript (though there is a proposal for it).
However, it is possible to implement the functionality you want in React. Consider something like this:
// state shape:
// {
// loading: boolean
// apiPromise: null | Promise<{ data: User[] }>
// users: User[]
// }
getUsersList = async (Filters) => {
// raw promise - don't await it yet
const apiPromise = getUsers({ Filters })
this.setState({ apiPromise, loading: true })
// _here_ we await it
const response = await apiPromise
// check if it's the most recent API request we made
if (this.state.apiPromise === apiPromise) {
this.setState({ users: response.data, loading: false })
} else {
console.log("stale val discarded:", users)
}
}
CodeSandbox demo - simplified example with mocked API and single val rather than list of users. Try clicking the button many times in quick succession and watch the console output.
Using CPromise wrapper the code might look like this See the live demo:
const CPromise = require('c-promise2');
const axios= require('axios');
// Let's wrap axios get method to the CPromise
function get(url){
return new CPromise((resolve, reject, {onCancel})=>{
axios.get(url, {
cancelToken: new axios.CancelToken(function executor(cancel) {
onCancel(cancel)
})
}).then(resolve, reject);
});
}
let chain= null;
function makeRequest(url){
chain && chain.cancel();
chain= get(url).then((response)=> {
console.log(`Response ${JSON.stringify(response.data)}`);
}, function (err) {
console.warn(`Error: ${err}`);
}
);
}
// some endpoint with a delay of 3 seconds for a response
const url= "https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=3s";
makeRequest(url);
// make the same request again, abort the previous
setTimeout(()=> makeRequest(url), 1000);
But since the package is in early beta stage, you can use the plain cancellation token, provided by axios See Axios documentation

Error: Can't set headers after they are sent node.js

hi guys im fairly new to node.js and I was wonder if I am making a call twice that I am unaware of. I am getting a Error: Can't set headers after they are sent.
export const hearingReminder = functions.https.onRequest((request, response) => {
console.log(request.body)
const payload = {
notification: {
title: 'Upcoming Hearing',
body: 'You have a hearing in one hour.',
}
};
const fcm = request.body.fcm
console.log(request.body.fcm)
try {
response.status(200).send('Task Completed');
return admin.messaging().sendToDevice(fcm, payload);
} catch (error) {
return response.status(error.code).send(error.message);
}
Your code is attempting to send a response twice, under the condition that admin.messaging().sendToDevice generates an error. Instead of sending the 200 response before the call, only send it after the call. Sending the response should always be the very last thing performed in your function.
Your code should be more like this:
admin.messaging().sendToDevice(fcm, payload)
.then(() => {
response.status(200).send('Task Completed');
})
.catch(error => {
response.status(error.code).send(error.message);
})
Note that you don't need to return anything for HTTP type functions. You just need to make sure to handle all the promises, and only send the response after all the promises are resolved.

Why my firebase cloud function process never finished?

I have this function for testing sending notification to all users.
export const sendTestingNotification = functions.https.onRequest((request, response) => {
const message = "Hello world";
const body = "This is body"
const getAllUsersPromise = admin.database().ref('/users').once('value')
const payload = {
notification: {
title: message,
body: body
}
}
return getAllUsersPromise.then(results => {
var tokens : string[] = [];
console.log("Child Snapshot count: ", results.numChildren())
results.forEach(childSnapshot => {
var childData = childSnapshot.val();
var instanceId = String(childData.instanceId)
if (childData.instanceId != null) { tokens.push(instanceId); }
})
console.log('final tokens = ',tokens," notification= ",payload);
return admin.messaging().sendToDevice(tokens, payload).then(response2 => {
console.log ("Done sending notification call. Entering callback.")
const tokensToRemove : string[] = [];
response2.results.forEach ((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to instance id = ', tokens[index], error);
}
else {
console.log("Successfully send notification to ", tokens[index])
}
});
return Promise.all(tokensToRemove);
})
.catch(console.log.bind(console));
})
.catch(console.log.bind(console));
});
I have only one user in firebase database, and that one user has instanceId.
Here's the console log:
Child Snapshot count: 1
final tokens = [ 'eMZHr5WgHmU:APA91bEKg8wAS5qYMxuSJqn...
Done sending notification call. Entering callback.
Successfully send notification to eMZHr5WgHmU:APA91bEKg8wAS5qYMxuSJqn...
Function execution took 60002 ms, finished with status: 'timeout'
What's left of my function execution so that it finishes properly? The browser that call the function never stops show loading indicator.
If I add a response.send before return Promise, the browser loading finished. But checking at the log showed me that the process still working and returned the "Function execution took 60002 ms, finished with status: 'timeout'" error. How can I fix this?
With HTTP type functions, you need to send a response to the caller to terminate the function. For all code paths in your function, you should be calling response.send() or something that will send that response. Please consult the documentation for more information an examples. In particular, read the section on terminating a function.

Simple Login Auth React with Redux

I'm not sure what I'm missing but I have a simple login page that verifies a user.
This is my LoginPage.js function that handles the login.
import { connect } from "react-redux";
import { loginUser } from "../actions/auth";
...
<< Class declarations >>
...
handleLogin = e => {
e.preventDefault();
const creds = {
username: e.target.username.value,
password: e.target.password.value
};
console.log("Login data sent ", creds);
loginUser(creds);
};
...
<< Login Component rendered and form to handleLogin >>
...
export default connect(
undefined,
{ loginUser }
)(Login);
Which is sent to /actions/auth.js
export const loginUser = creds => {
console.log("login user creds ", creds);
return dispatch => {
console.log("inside dispatch ", creds);
try {
let response = API.post("api/login", {
username: "eve.holt#reqres.in",
password: "cityslicka"
});
console.log("Returned Data ", response);
dispatch(receiveLogin(creds));
return response;
} catch (e) {
console.log("Axios request failed ", e);
return false;
}
};
};
I have put console logs in to see where it goes but I only get:
Login data sent {username: "test", password: "test"}
login user creds {username: "test", password: "test"}
It doesn't seem to go any further than that so I don't see anything inside the dispatch.
Edit: I added that I am actually already using connect in the LoginPage.js
There are couple of updates we need to make in order for your login feature to work.
You're probably already somewhat familiar with the React-Redux flow.
A user interacts with your component, they trigger an event
(submitting a form/login).
We handle the event by calling our dispatcher function (the function we imported and plugged in connect()), taking the
user inputs to formulate a request to the back-end API. (redux-thunk action-creator)
We wait for the back-end API to verify the user credentials, if
successful they will give us back a token or user payload. (Promise-handler)
Use the returned object to dispatch an action, some info for our reducer to
handle. (Actual dispatch of action)
So let's try to create something to resemble that flow. To start, it looks like you've partially set-up an event-handler. The problem is that the loginUser() action-creator does not implicitly have access to the dispatch method. You need to call it from your props:
handleLogin = e => {
e.preventDefault();
const creds = {
username: e.target.username.value,
password: e.target.password.value
};
console.log("Login data sent ", creds);
this.props.loginUser(creds);
};
this.props.loginUser has dispatch binded to it thanks to the connect(), where as directly calling it from the import will not yield any redux functionality. This should be enough to complete 1 and 2.
Next we need to resolve number 3. At this point you should be able to execute the logic inside the dispatch function. However, the logic is not synchronous (ie, the flow of execution is not controlled), it does not wait for your API to give us something back before continuing.
You can introduce async/await for Promise-handling. In a nut-shell, it means to wait for something to complete before moving forward. Note that this will only work for promises. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
export const loginUser = creds => {
console.log("login user creds ", creds);
return async (dispatch) => {
console.log("inside dispatch ", creds);
try {
let response = await API.post("api/login", {
username: "eve.holt#reqres.in",
password: "cityslicka"
});
console.log("Returned Data ", response);
dispatch(receiveLogin(creds));
return response;
} catch (e) {
console.log("Axios request failed ", e);
return false;
}
};
};
For number 4, I will have to let you decide how to pass that data to your reducer :)
Your logic has a problem, I guess you want post request, if you get the correct response then dispatch. There is a function returned in your function. It doesn't make any sense to do this. Just execute it.
and use the dispatch function as a parameter is ok.

How to add results from a promise based API call with message.addReply using Recast.ai?

I'm making a bot that searches restaurants based on location. Can anyone help me why this doesnt show up in FB messenger?:
restaurants(result.getMemory('location').raw)
.then(res=>{
message.addReply(res);
message.reply();
});
}
The call to the restaurants function returns the results from a YELP API call (an array of restaurants) but when I add it as a reply to message, nothing happens in FB messenger.
Here is the full code for message.js:
const recastai = require('recastai');
const restaurants = require('./restaurants');
// This function is the core of the bot behaviour
const replyMessage = (message) => {
// Instantiate Recast.AI SDK, just for request service
const request = new recastai.request(process.env.REQUEST_TOKEN,
process.env.LANGUAGE);
// Get text from message received
const text = message.content;
console.log('I receive: ', text);
// Get senderId to catch unique conversation_token
const senderId = message.senderId;
// Call Recast.AI SDK, through /converse route
request.converseText(text, { conversationToken: senderId })
.then(result => {
//Recast takes text analyses that, returns a result object, generates replies adds messages to reply stack and then sends the replies
//Call Yelp API with when the intent is Location. When Yelp returns result we add it to the result.replies array.
//Then we add everything in result.replies to the messaging queue that sends the responses to FB
if (result.action) {
console.log('The conversation action is: ', result.action.slug);
}
// If there is not any message return by Recast.AI for this current conversation
if (!result.replies.length) {
message.addReply({ type: 'text', content: 'I don\'t have the reply to this yet :)' });
} else {
// Add each reply received from API to replies stack
result.replies.forEach(replyContent => message.addReply({ type: 'text', content: replyContent }));
}
// Send all replies
message.reply()
//send initial reply generated by Recast first
.then(() => {
//call restaurant function that returns a list of results from API
//if the action is location and done
if(result.action && result.action.slug === 'location' && result.action.done){
restaurants(result.getMemory('location').raw)
.then(res=>{
console.log(res);
message.addReply(res);
message.reply();
});
}
})
.catch(err => {
console.error('Error while sending message to channel', err);
});
})
.catch(err => {
console.error('Error while sending message to Recast.AI', err);
});
};
module.exports = replyMessage;
And here is my restaurants.js code that is imported into the message.js file for the bot behavior:
const rp = require('request-promise');
// Load configuration
require('./config');
const restaurants = (location) => {
return Promise.all([
yelpCall(location)
]).then(result => {
//result contains the return value from Yelp call
return result;
});
};
const yelpCall = (location) => {
const auth = {
method: 'POST',
url: 'https://api.yelp.com/oauth2/token?grant_type=client_credentials&client_id='+ process.env.YELP_APP_ID +'&client_secret='+process.env.APP_SECRET
};
return rp(auth)
.then(result => {
const tokens = JSON.parse(result);
return tokens;
})
.then(result=>{
const options = {
url: 'https://api.yelp.com/v3/businesses/search?location=' + location + "&term=thai",
headers: {Authorization: "Bearer " + result.access_token}
};
return rp(options).then(findings =>{
return findings;
});
});
};
module.exports = restaurants;
A few thoughts :
message.reply is thenable, therefore return message.reply() in two places.
request.converseText() is thenable, therefore return request.converseText(...).
restaurants is thenable, therefore return restaurants(...).
in message.js, message.addReply() is passed object of the form {type:..., content:...} in two places but finally just res. Is that correct?
in restaurants.js, Promise.all() appears to be unnecessary. It will cause its result to be wrapped in an array. module.exports = location => yelpCall(location); seems more appropriate.

Categories

Resources