I have a method called fetchMerchantData which calls 3 other async methods. I'm trying to use Promise so that it doesn't call resp.direct(301, ...) until all the requests are finished but it's not working.
function fetchOauth2Token(authorizationCode, resp) {
...
request({
url: `https://connect.squareup.com/oauth2/token`,
method: "POST",
json: true,
headers: oauthRequestHeaders,
form: oauthRequestBody,
}, (error, oauthResp, body) => {
if (body.access_token) {
Promise.resolve(fetchMerchantData(body.access_token, body.merchant_id)).then(() => {
console.log("last!"); //<--------------------- this is printing first
resp.redirect(
301,
`myurl.com/blah`
);
});
;
} else {
// TODO find out what to do on failure
resp.redirect(301, `myurl.com/?error=true`);
}
})
}
function fetchMerchantData(access_token, merchant_id){
const updates = {};
request({
url: `https://connect.squareup.com/v1/me/locations`,
method: "GET",
json: true,
headers: {
Authorization: `Bearer ${access_token}`,
Accept: 'application/json',
"Content-Type": "application/json",
},
}, (error, response) => {
if (!error) {
const locations = response.body;
Promise.all([
saveMerchant(merchant_id, access_token, locations),
saveLocations(merchant_id, locations),
installWebhookForLocations(access_token, locations),
]).then(values => {
console.log("first!"); //<--------------------- this is printing last
return Promise.resolve("Success");
})
}
});
}
And here's an example of the saveMerchant method which calls firebase:
function saveMerchant(merchant_id, access_token, locations) {
const merchantRef = database.ref('merchants').child(merchant_id);
const location_ids = locations.map(location => location.id);
merchantRef.update({
access_token,
location_ids,
});
}
How would I synchronize this?
== UPDATE ==
This is how my installWebhookForLocations method looks:
function installWebhookForLocations(access_token, locations){
const locationIds = locations.map(location => location.id);
locationIds.forEach((locationId) => {
request({
url: `https://connect.squareup.com/v1/${locationId}/webhooks`,
method: "PUT",
json: true,
headers: {
Authorization: `Bearer ${access_token}`,
Accept: 'application/json',
"Content-Type": "application/json",
},
body: ["PAYMENT_UPDATED"],
}, (error) => {
if (!error){
console.log(`Webhook installed for ${locationId}`);
}
});
});
}
Here is an example of saveMerchant that would use a promise.
function saveMerchant(merchant_id, access_token, locations) {
return new Promise(function (resolve, reject) {
const merchantRef = database.ref('merchants').child(merchant_id);
const location_ids = locations.map(location => location.id);
merchantRef.update({
access_token,
location_ids,
}, function (error) {
if (error) return reject(error);
resolve();
});
});
}
To make the above easier, there is a nice Promise library called Bluebird, it has a promisify utility, that you could apply to firebird update method.
Also for your second question were your using forEach, bluebird has a nice utility function called map that you could use instead.
Related
Minimum reproducible code:
index.ts:
import * as admin from "firebase-admin"
import fetch, { Headers } from "node-fetch";
interface BarPayload {
topic: string,
token: string,
}
exports.bar = functions.https.onCall(async (data, context) => {
if (data != null) {
const payload: BarPayload = {
topic: data.topic,
token: data.token,
}
const url = `https://${location}-${project}.cloudfunctions.net/subscribeToTopic`
await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
topic: payload.topic,
token: payload.token,
}),
})
}
return null;
});
export const subscribeToTopic = functions.https.onRequest(async (req, res) => {
const payload = req.body as BarPayload;
fetch('https://iid.googleapis.com/iid/v1/' + payload.token + '/rel/topics/' + payload.topic, {
method: 'POST',
headers: new Headers({
'Authorization': 'key=AA...Wp9',
'Content-Type': 'application/json'
})
}).then(response => {
if (response.status < 200 || response.status >= 400) {
res.sendStatus(299)
}
}).catch(error => {
console.error(error);
res.sendStatus(299)
})
return Promise.resolve();
})
I'm running bar in Flutter and I see the timeout error in Logs Explorer:
textPayload: "Function execution took 60051 ms. Finished with status: timeout"
But if I change my subscribeToTopic from HTTP function to a callable function, then it works fine. For example:
exports.subscribeToTopic = functions.https.onCall(async (data, context) => {
fetch('https://iid.googleapis.com/iid/v1/' + data.token + '/rel/topics/' + data.topic, {
method: 'POST',
headers: new Headers({
'Authorization': 'key=AA...Wp9',
'Content-Type': 'application/json'
})
}).then(response => {
if (response.status < 200 || response.status >= 400) {
console.log('Error = ' + response.error);
}
}).catch(error => {
console.error(error);
})
return null;
});
(I know I'm making some trivial mistake, and I'm new to Typescript. Any help would be appreciated :)
You should not do return Promise.resolve(); in the HTTPS Cloud Function:
HTTPS Cloud Functions shall be terminated with with send(), redirect() or end();
return Promise.resolve(); is executed before the asynchronous call to fetch is complete.
The following should do the trick (untested):
export const subscribeToTopic = functions.https.onRequest(async (req, res) => {
try {
const payload = req.body as BarPayload;
const response = await fetch('https://iid.googleapis.com/iid/v1/' + payload.token + '/rel/topics/' + payload.topic, {
method: 'POST',
headers: new Headers({
'Authorization': 'key=AA...Wp9',
'Content-Type': 'application/json'
})
});
if(response.status < 200 || response.status >= 400) {
res.status(299).send();
}
} catch (error) {
res.status(400).send();
}
})
However I don't understand why you separate your business logic in two Cloud Functions. Why don't you directly fetch https://iid.googleapis.com within the bar Callable Cloud Function?
I am using next js with axios, and I have a .then promise inside a .then. I want to respond to the api request with the data I grabbed but if I put the res in the .then it is unreachable, but I don't know how to get the data out of the .then. Normally with async await I return the value and create a variable to the function but this does not seem to work with .then.
here is my code
let data;
axios
.post(
"https://accounts.spotify.com/api/token",
serialize({
grant_type: "client_credentials",
}),
{
headers: {
Authorization: "Basic " + auth,
},
}
)
.then((res) => {
axios
.get(
`https://api.spotify.com/v1/search?q=${name}&type=episode&market=US`,
{
headers: {
Authorization: `Bearer ${res.data.access_token}`,
"Content-Type": "application/json",
},
}
)
.then(function (response) {
// handle success
console.log(response.data.episodes);
data = response.data.episodes;
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
})
.catch((err) => {
console.log(err);
});
res.status(200).json({ data: data });
Any help would be greatly appreciated thanks
The following fixed it
const data = axios
.post(
"https://accounts.spotify.com/api/token",
serialize({
grant_type: "client_credentials",
}),
{
headers: {
Authorization: "Basic " + auth,
},
}
)
.then((res) => {
const test = axios
.get(
`https://api.spotify.com/v1/search?q=${name}&type=episode&market=US`,
{
headers: {
Authorization: `Bearer ${res.data.access_token}`,
"Content-Type": "application/json",
},
}
)
.then(function (response) {
// handle success
// console.log(response.data.episodes);
return response.data.episodes;
})
.catch(function (error) {
// handle error
console.log(error);
});
return test;
})
.catch((err) => {
console.log(err);
});
const boy = async () => {
console.log("data", await data);
res.status(200).json({ data: await data });
};
boy();
I have the code below which is in a Vue script.
user_id = 100; //sample data
$.ajax({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
type: "GET",
url: '/user',
success: function (user) {
user_id = user.user_id;
console.log(user_id); //returns 1
},
error: function (result) {
}
});
console.log(user_id); //returns 100 not 1
I want to be able to store the value that is resulted from the ajax request which is 1. However, when I console.log at the end of the script it returns 100 not 1. I think that I need to use a promise/callback to solve this but I am not sure how/what I need to do. Can someone help me?
Define your method and return as a promise.
function getUsers() {
return new Promise((resolve, reject) => {
$.ajax({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
type: "GET",
url: '/user',
success: function (data) {
resolve(data);
},
error: function (error) {
reject(error);
}
});
});
}
You would call the method as below.
getUsers().then((data) => {
console.log(data); /* you will get the new data returned from ajax.*/
}).catch((error) => {
console.log(error);
});
This is how you can promisify callbacks in general:
let doXWithCallback = callback => {
// do x...
callback();
};
let doXPromisified = () => new Promise(doXWithCallback);
doXWithCallback(() => console.log('do x with callback'));
doXPromisified().then(() => console.log('do x promisified'));
For your example specifically:
let doRequest = () =>
new Promise((resolve, reject) =>
$.ajax({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
type: "GET",
url: '/user',
success: user => resolve(user.user_id),
error: reject(),
}));
doRequest.then(userId => console.log('userId is', userId));
I am new to NodeJS and I am working on a request.get problem. My goal is simply have a function that request the web, and when request finished, the function returns the result, otherwise it returns an error message.
Here's the function that I used for request:
var artistNameIdMap = {};
var getPopularArtists = async () => {
//https://nodejs.org/api/http.html#http_http_request_options_callback
var options = {
url: CONSTANTS.API_ENDPOINTS.playlist_endpoint + subpath,
headers: { 'Authorization': 'Bearer ' + access_token,
'Accept': 'application/json',
'Content-Type': 'application/json'},
json: true
}
request.get(options, function(error, response, body) {
if (response.statusCode === 200){
console.log("inside");
artistNameIdMap = getArtistNameIdMap(body, artistNameIdMap);
} else {
res.send("get popular error");
return {};
}
})
console.log("outside");
return artistNameIdMap;
module.exports = {
GetPopularArtists: getPopularArtists
}
And this function is included in a getPopular.js file. I would like to call the function in another file playlist.js.
In playlist.js, I wrote
const getPopular = require('./controllers/getPopular');
router.get("/BPM/:BPM", (req, res) =>{
const artistNameIdMap = getPopular.GetPopularArtists();
console.log(artistNameIdMap);
let BPM = req.params.BPM;
res.send(BPM);
})
However the result I got is
outside
Promise { {} }
inside
It seems like the return was before the request gives back the information. I wonder what should I write to make sure that I can obtain the correct artistNameIdMap at playlist.js.
Though you've already accepted an answer, there are a couple of additional things I can add. First, the request() library has been deprecated and it is not recommended for new code. Second, there is a list of recommended alternatives here. Third, all these alternatives support promises natively as that is the preferred way to program asynchronous code in modern nodejs programming.
My favorite alternative is got() because I find it's interface simple and clean to use and it has the features I need. Here's how much simpler your code would be using got():
const got = require('got');
let artistNameIdMap = {};
async function getPopularArtists() {
const options = {
headers: { 'Authorization': 'Bearer ' + access_token,
'Accept': 'application/json',
'Content-Type': 'application/json'},
};
const url = CONSTANTS.API_ENDPOINTS.playlist_endpoint + subpath;
let results = await got(url, options).json();
// update local cache object
artistNameIdMap = getArtistNameIdMap(results, artistNameIdMap);
return artistNameIdMap;
}
module.exports = {
GetPopularArtists: getPopularArtists
}
Note: The caller should supply error handling based on the returned promise.
GetPopularArtists().then(results => {
console.log(results);
}).catch(err => {
console.log(err);
});
Since you want to use Promises, use it like this
const getPopularArtists = () => new Promise((resolve, reject) {
const options = {
url: CONSTANTS.API_ENDPOINTS.playlist_endpoint + subpath,
headers: {
'Authorization': 'Bearer ' + access_token,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
json: true
}
request.get(options, (error, response, body) => {
if (error) {
reject(error);
} else if (response.statusCode === 200) {
console.log("inside");
resolve(getArtistNameIdMap(body, artistNameIdMap));
} else {
reject("get popular error");
}
});
});
module.exports = {
GetPopularArtists: getPopularArtists
}
And use it like
const getPopular = require('./controllers/getPopular');
router.get("/BPM/:BPM", async (req, res) =>{
try {
const artistNameIdMap = await getPopular.GetPopularArtists();
console.log(artistNameIdMap);
let BPM = req.params.BPM;
res.send(BPM);
} catch(err) {
res.send(err);
}
})
Alternatively, without promises, you'll need to use a callback
Using callbacks:
const getPopularArtists = (callback) => {
const options = {
url: CONSTANTS.API_ENDPOINTS.playlist_endpoint + subpath,
headers: { 'Authorization': 'Bearer ' + access_token,
'Accept': 'application/json',
'Content-Type': 'application/json'},
json: true
}
request.get(options, function(error, response, body) {
if (error) {
callback(error);
} else if (response.statusCode === 200){
console.log("inside");
callback(null, getArtistNameIdMap(body, artistNameIdMap));
} else {
callback("get popular error");
}
})
};
module.exports = {
GetPopularArtists: getPopularArtists
}
And use it like:
const getPopular = require('./controllers/getPopular');
router.get("/BPM/:BPM", (req, res) =>{
getPopular.GetPopularArtists((err, artistNameIdMap) => {
if (err) {
// handle error here
} else {
console.log(artistNameIdMap);
let BPM = req.params.BPM;
res.send(BPM);
}
});
});
I'm send a post request and I get the response like this
" [Symbol(Response internals)]: {
url: 'https://login.somenewloginpage'}"
and what I want to do is I want to open a new page via that url but it does not direct to the new page.
const login= () => async () => {
const api = `somePostRequest`
fetch(api, {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-url-encoded',
Accept: 'application/json',
},
})
.then(function(res) {
return res //maybe I should do something in this part...
})
.then(data => console.log(data));
};
Here's how to use fetch() with async/await syntax :
const login= () => async () => {
const api = `somePostRequest`;
const response = await fetch(api, {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-url-encoded',
Accept: 'application/json',
},
});
const data = await response.json(); // { url: 'https://login.somenewloginpage'}
window.location.replace(data.url); // <-- Redirection
};