I cannot figure out how should I construct my code.
Basic info:
webhook (intercom) --> google cloud functions (await values) --> post message to slack.
Issue:
My code is working fine until I need to get an value from another await function and I am not sure how should I 'pause' the second await until the first one is complete.
Code:
// first function to get the information about agent
const getTeammateInfo = async function (teammate_id) {
try {
const response = await axios.get("https://api.intercom.io/admins/" + teammate_id, {
headers: {
'Authorization': "Bearer " + INTERCOM_API_AUTH_TOKEN,
'Content-type': "application/json",
'Accept': "application/json"
}
});
const { data } = response
return data
} catch (error) {
console.error(error);
}
};
// second function, which needs values from first function in order to find data
const slackID = async function (slack_email) {
if (slackID) {
try {
const response = await axios.get("https://api.intercom.io/admins/" + slack_email, {
headers: {
'Authorization': "Bearer " + SLACK_API_TOKEN,
'Content-type': "application/json",
'Accept': "application/json"
}
});
const { user } = response
return user
} catch (error) {
console.error(error);
}
}
};
It is used within the Google Cloud Function (
exports.execute = async (req, res) => {
try {
// map of req from intercom webhook
let {
data: {
item: {
conversation_rating: {
rating,
remark,
contact: {
id: customerId
},
teammate: {
id: teammateId
}
}
}
}
} = req.body
const teammateName = await getTeammateInfo(teammateId); // this works fine
const slackTeammateId = await slackID(teammateName.email) // this is where it fails I need to get the values from 'teammateName' in order for the function slackID to work
...
} catch (error) {
console.error(error)
res.status(500)
res.json({ error: error })
}
}
I have tried with Promise.all
const [teammateName, slackTeammateId] = await Promise.all([
getTeammateInfo(teammateId),
slackID(teammateName.email)
])
But I just cannot wrap my head around this how it should work.
Thank you.
// edit:
Code is okay, I just put the wrong API into the slackID function...
Thanks for double checking.
The problem with Promise.all() is that it fires both functions at the same time and waits until both are done, so you can't chain that way.
let teammateName
let slackTeammateId
getTeammateInfo(teammateId).then(r => {
teammateName = r
slackID(teammateName.email).then(r2 => {
slackTeammateId = r2
)}
);
then() method, on the other hand, waits until your method's return and then fires out everything in the callback function.
Related
I have this async function to get an API access token:
const getAccessToken = async () => {
try {
const body = new URLSearchParams({
grant_type: 'client_credentials',
scope: 'manage:all'
}).toString();
const config = {
headers: {
Content_Type: 'application/x-www-form-urlencoded'
},
auth: {
username: clientId,
password: clientSecret
}
};
const { data: res } = await axios.post(
`${baseUrl}/oauth2/token`,
body,
config
);
return res;
} catch (err) {
console.log(err);
}
};
It return a promise which I use in the following function to log the access token to the console:
getAccessToken().then(res => {
console.log(res.access_token);
});
It logs the token as a string.
Now I want to use this string value in another function in my code. Do I just call the function where I need the value like above and replace the console.log with return?
How do I use this string value in other parts of my code?
One way of doing it to call the function everywhere,
async function anotherFunction() {
const res = await getAccessToken();
console.log(res.access_token)
}
Another way of doing this is to call the function on your page load save the token in session/local storage/cookies and use the token in other functions by fetching it from session/local storage/cookies to avoid making multiple API calls.
On page load
const res = await getAccessToken();
localStorage.setItem('token', res.access_token);
async function anotherFunction() {
const res = localStorage.getItem('token');;
console.log(res.access_token)
}
Recently updated SWR - now for some reason my data is not fetching properly.
const { data: expressionsData, error: expressionsError } = useSWRImmutable(
[`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
apiRequest
);
Using this fetching,
import firebase from "./firebase";
export async function apiRequest(path, method = "GET", data) {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
//this is a workaround due to the backend responses not being built for this util.
if (path == "dashboard/get-settings") {
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.error === "error") {
throw new CustomError(response.code, response.messages);
} else {
return response;
}
});
}
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
console.log("error", response);
if (response.status === "error") {
// Automatically signout user if accessToken is no longer valid
if (response.code === "auth/invalid-user-token") {
firebase.auth().signOut();
}
throw new CustomError(response.code, response.message);
} else {
return response.data;
}
});
}
// Create an Error with custom message and code
export function CustomError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
// Check if a indexDb database exists
export function indexedDbdatabaseExists(dbname, callback) {
const req = window.indexedDB.open(dbname);
let existed = true;
req.onsuccess = function () {
req.result.close();
if (!existed) window.indexedDB.deleteDatabase(dbname);
callback(existed);
};
req.onupgradeneeded = function () {
existed = false;
callback(existed);
};
}
Now I'm looking at this StackOverflow thread,
useSWR doesn't work with async fetcher function
And thinking I'll just remake the fetcher to be without Async. I'm just wondering why this has stopped working though in general, and if I can just keep my existing codebase.
The error is a 400 message, it only happens with this expressions API call which takes longer to load due to the amount of data I think,
xxxx/dashboard/expression/get-expression-analytics?startTime=1648183720488&endTime=1650865720488 400 (Bad Request)
with error log
These calls are working fine, they have substantly less data though.
const { data: overall, error: psychometricError } = useSWRImmutable(
`dashboard/psychometric/get-psychometric-home?starttime=infinite`,
apiRequest
);
const { data: sentimentData, error: sentimentError } = useSWRImmutable(
[`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
fetchSentiment
);
Made an update to the fetch call to be more readable and specifically about the URL pathway.
import firebase from './firebase';
// Create an Error with custom message and code
export function CustomError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
export async function expressionsRequest(path, method = 'GET') {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
return fetch(`/api/${path}`, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => {
if (!response.ok) {
throw `Server error: [${response.status}] [${response.statusText}] [${response.url}]`;
}
return response.json();
})
.then((receivedJson) => {
if (receivedJson.status === 'error') {
// Automatically signout user if accessToken is no longer valid
if (receivedJson.code === 'auth/invalid-user-token') {
firebase.auth().signOut();
}
throw new CustomError(receivedJson.code, receivedJson.message);
} else {
return receivedJson.data;
}
})
.catch((err) => {
console.debug('Error in fetch', err);
throw err;
});
}
Additionally, this is what the lambda function (using next API folder) looks like,
const requireAuth = require('../../_require-auth');
const { db } = require('../../_sql');
export default requireAuth(async (req, res) => {
const { uid: id } = req.user;
const startTime = Math.round(req.query.startTime * 0.001);
const endTime = Math.round(req.query.endTime * 0.001);
const parameters = [id, startTime, endTime];
//sql injection definitely possible here, need to work out better method of dealing with this.
const sqlText = `SELECT a,b,c,d,e,f,g,h,i FROM tablename WHERE a=$1 AND i BETWEEN $2 AND $3;`;
try {
const { rows } = await db.query(sqlText, parameters);
return res.status(200).json({
code: 0,
data: rows,
});
} catch (error) {
return res.status(200).json({
code: 0,
message: 'Error occurred in getting tablename',
error,
});
}
});
using postman with the same query, i.e.,
curl --location --request GET 'http://localhost:3000/api/dashboard/expression/get-expression-analytics?startTime=1648387240382&endTime=1651069240382' \
--header 'Authorization: Bearer xxxx' \
--data-raw ''
Successfully returns a response with data attached.
Based on your first code blocks, the startDate value is getting passed into the fetcher as method, and the endDate value is getting passed into the fetcher as data. This is based on the useSWR docs about passing in an array for the key argument: https://swr.vercel.app/docs/arguments#multiple-arguments
If the code you provided is correct, I'd assume the 400 is coming from trying to pass in a random value for the method option for fetch.
This should be fixed by only passing the API endpoint path into useSWR instead of an array:
const { data: expressionsData, error: expressionsError } = useSWRImmutable(
`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`,
apiRequest
);
Multiple people have brought up issues similar to mine in this community and cloudflare's community. It still seems largely unsolved so I’m asking in hopes of a solution.
I’m trying to create a feature for users to sign up through mailchimp. User info goes from browser to workers to mail chimp. I’m getting the following errors:
TypeError: Failed to execute function: parameter 1 is not of type
‘Response’. at line 0, col -2
Request.Body is not being read
Request from Client:
const response = await axios({
method: "post",
url: "http://127.0.0.1:8787/signup",
data: {
MERGE0: email,
MERGE1: firstName,
MERGE2: lastName,
},
headers: {
"Content-Type": "application/json",
}
});
Workers part 1 (function to read request body):
https://developers.cloudflare.com/workers/examples/read-post
async function readRequestBody(request) {
const { headers } = request
const contentType = headers.get('content-type') || ''
if (contentType.includes('application/json')) {
return JSON.stringify(await request.json())
} else if (contentType.includes('application/text')) {
return request.text()
} else if (contentType.includes('text/html')) {
return request.text()
} else if (contentType.includes('form')) {
const formData = await request.formData()
const body = {}
for (const entry of formData.entries()) {
body[entry[0]] = entry[1]
}
return JSON.stringify(body)
} else {
// Perhaps some other type of data was submitted in the form
// like an image, or some other binary data.
return 'a file'
}
}
Workers part 2 (to Post JSON File to Mail Chimp):
https://developers.cloudflare.com/workers/examples/post-json
async function gatherResponse(response) {
const { headers } = response
const contentType = headers.get('content-type') || ''
if (contentType.includes('application/json')) {
return JSON.stringify(await response.json())
} else if (contentType.includes('application/text')) {
return response.text()
} else if (contentType.includes('text/html')) {
return response.text()
} else {
return response.text()
}
}
Workers Part 3 (to Handle Post Request):
async function eventHandler(request) {
const pathname = request.url
try {
if (pathname.indexOf('signup') !== -1) {
const reqBody = await readRequestBody(request)
const { MERGE0, MERGE1, MERGE2 } = reqBody
// Construct req data
const data = {
members: [
{
email_address: MERGE0,
status: 'subscribed',
merge_fields: {
FNAME: MERGE1,
LNAME: MERGE2,
},
},
],
}
const postData = JSON.stringify(data)
const options = {
method: 'POST',
headers: {
Authorization: `auth ${MAILCHIMP_API_KEY}`,
},
body: postData,
}
const url = `https://us5.api.mailchimp.com/3.0/lists/${MAILCHIMP_AUDIENCE_ID}`
const res = await fetch(url, options)
const results = await gatherResponse(res)
return results
}
} catch (err) {
console.log(err)
}
}
addEventListener('fetch', event => {
event.respondWith(eventHandler(event.request))
})
A few other posts I’ve referenced:
https://community.cloudflare.com/t/fetch-with-post-method-ignores-body/147758/3
https://community.cloudflare.com/t/how-to-post-with-a-body-as-readable-stream/211335
https://community.cloudflare.com/t/using-get-fetch-for-api-javascript-worker/98297
You have this code:
event.respondWith(eventHandler(event.request))
The event.respondWith() function needs to take a Response object as its parameter. However, your eventHandler() function does not return a Response. In some cases, the function does not return a result at all, and in other cases, it returns a string.
In cases where you don't want to modify the request/response, you can have eventHandler pass through the request to origin like so:
return fetch(request);
In cases where you have received a response from the origin, and you want to return it directly to the client unmodified, you should just return repsonse instead of return response.text().
In cases where you have created some new response text that you want to return, you need to wrap it in a Response, like:
return new Respnose(text, {headers: {"Content-Type": "text/plain"}});
I'm learning nodejs and trying to make an API call. The API uses JWT to authenticate.
I created these functions to sign a token:
function token() {
const payload = {
iat: Math.floor(new Date() / 1000),
exp: Math.floor(new Date() / 1000) + 30,
sub: "api_key_jwt",
iss: "external",
jti: crypto.randomBytes(6).toString("hex")
};
return new Promise((resolve, reject) => {
jwt.sign(payload, privatekey, { algorithm: "RS256" }, function(
err,
token2
) {
if (err) reject(err);
else resolve(token2);
});
});
}
exports.genToken = async function() {
const header = {
"x-api-key": api
};
const data = {
kid: api,
jwt_token: await token()
};
async function authorization(req, res) {
try {
const auth = await rp({
url: authurl,
method: "POST",
headers: header,
body: data
});
res.send(auth.body);
} catch (error) {
res.send(404).send();
}
}
return {
"x-api-key": api,
Authorization: "Bearer " + authorization()
};
};
This works fine. Then I created a function to make the API call:
const token = require("./index").genToken;
const rp = require("request-promise");
exports.getOrderBook = function(res, error) {
const full_url = url + "order_book";
const auth = token();
rp({
url: full_url,
method: "GET",
headers: auth,
body: {
market: "btceur"
},
json: true
})
.then(function(response) {
res(response);
})
.catch(function(err) {
error(err);
});
};
And I call it using Express:
routes.get("/orderbook", async (req, res, next) => {
try {
const book = await orders.getOrderBook();
res.send(book);
} catch (error) {
next(error);
}
});
However, when I call my API, it shows an error in console:
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be one of
type string or Buffer. Received type object.
I guess the error is something with the token generation, because if I console.log(auth) in the getOrderBook function, it shows Promise { <pending> }, so probably an object is being passed as the jwt token.
Is it really the problem? I tried a lot of different solutions that I found on internet, however the concept of Async/Await is new to me, and I'm having some troubles to figure it out.
Thanks a lot in advance guys!
Since getToken is an anync function, the return is wrapped in a Promise as well so you would need another anync/await:
exports.getOrderBook = async function() {
let response;
try {
const full_url = url + "order_book";
const auth = await token();
response = await rp({
url: full_url,
method: "GET",
headers: auth,
body: {
market: "btceur"
},
json: true
});
} catch (e) {
// handle error
throw e
// or console.error(e)
}
return response;
};
In this line as well Authorization: "Bearer " + authorization(), authorization is returning a promise
const bearer = await authorization()
return {
"x-api-key": api,
Authorization: "Bearer " + bearer
};
For error handling wrap entire thing in try..catch block
exports.genToken = async function() {
try {
const header = {
"x-api-key": api
};
const data = {
kid: api,
jwt_token: await token()
};
async function authorization(req, res) {
let auth;
try {
auth = await rp({
url: authurl,
method: "POST",
headers: header,
body: data
});
// res object not available
// res.send(auth.body);
} catch (error) {
// res object not available, better throw error and handle in your middleware
// res.send(404).send();
}
return auth
}
const bearer = await authorization()
} catch (e) {
// handle error
}
return {
"x-api-key": api,
Authorization: "Bearer " + bearer
};
}
I'm trying to get JSON object from axios
'use strict'
async function getData() {
try {
var ip = location.host;
await axios({
url: http() + ip + '/getData',
method: 'POST',
timeout: 8000,
headers: {
'Content-Type': 'application/json',
}
}).then(function (res) {
console.dir(res); // we are good here, the res has the JSON data
return res;
}).catch(function (err) {
console.error(err);
})
}
catch (err) {
console.error(err);
}
}
Now I need to fetch the res
let dataObj;
getData().then(function (result) {
console.dir(result); // Ooops, the result is undefined
dataObj = result;
});
The code is blocking and waits for the result, but I'm getting undefined instead of object
This seems to be one of those cases where async/await doesn't buy you much. You still need to return a result from the async function, which will return a promise to the caller. You can do that with something like:
async function getData() {
try {
let res = await axios({
url: 'https://jsonplaceholder.typicode.com/posts/1',
method: 'get',
timeout: 8000,
headers: {
'Content-Type': 'application/json',
}
})
if(res.status == 200){
// test for status you want, etc
console.log(res.status)
}
// Don't forget to return something
return res.data
}
catch (err) {
console.error(err);
}
}
getData()
.then(res => console.log(res))
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>
But in this example, since you don't need to do much in the actual function with the result, you're probably better off just returning axios's promise:
function getDataPromise() {
return axios({
url: 'https://jsonplaceholder.typicode.com/posts/1',
method: 'get',
timeout: 8000,
headers: {
'Content-Type': 'application/json',
}
})
.then(res => res.data)
.catch (err => console.error(err))
}
getDataPromise()
.then(res => console.log(res))
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>
It is not what you would like to hear but
Async/Wait works on the principle "Whatever Happens in Vegas - Stays in Vegas". It means you could not pass benefits of using blocking IO calls outside of async block.
If you want to use async/await to create some kind of blocking IO call it would not work unless a block caller is also inside an async function what is not normally the case.
async/wait is only good if you want to have a long chain of IO calls but entire chain still MUST be non-blocking. Individual calls inside chain might be blocking but full chain not.
Example
async fn(url) { //this is a non blocking function
let res = await axios.get("http://jsonservice1"); //blocking but only inside this function
let res2 = await axios.get(url+'?s='+res.data);//res.data is resolved already
return res2; //this how it returns results but it will not be resolved until .then is called what is effectively a callback
}
fn("google.com").then(R=>console.log('sorry I am not blocking '+R.data));
Coming from ajax, I prefer modular approach. Data to be sent, function on success and function on fail are separate from function using axios. Bellow sample code fetch user email against user name form node.js and mysql at the backend.
HTML: <button onclick=" testaxios();">TestAxios</button>
JS in browser:
var data = {
username: "myusername"
}
async function testaxios() {
try {
let res = await axios({
method: 'POST',
data: data,
url: '/testmysql',
});
if (res.status == 200) {
success(res);
};
}
catch (error) {
fail(error);
};
}
function success(res) {
console.log(res.data[0][0].email);
}
function fail(error) {
console.log(error);
}
JS in nodeJS at backend:
app.post("/testmysql", async function (req, res) {
try {
let usrname = req.body.username;
let em = await pool.query("SELECT email FROM users WHERE username = ?", usrname);
res.send(em);
} catch (err) {
console.log(err);
} });