I'm working with AWS Lambda. I'm trying to decrypt the key in a async function & then making a POST request to another URL which depends on getting the key first from the first call.
module.exports = function (payload) {
return new Promise(function(resolve, reject) {
var headers = config.Headers;
decrypt(headers.Authorization)
.then(function (auth_token) {
headers.Authorization = 'Basic ' + auth_token;
console.log('dispatch got the key from kms');
return makePostCall(headers, payload);
})
.then(function (changeNumber){
console.log(changeNumber);
return resolve(changeNumber);
})
.catch (function (error) {
console.error('Error during dispatch: ' + error);
return reject(error);
});
});
};
The decrypt & makePostCall call both return either reject or resolve. Runs fine locally, but when running on Lambda, runs successfully only some times, which led me to believe that issue is with asynchronous calling of the makePostCall function. The error I get(from catch) is:
Error during dispatch: null
I need to run decrypt first-> Get the key -> then MakePostCall.
Edit: makePostCall looks like this:
function makePostCall(headers, payload) {
return new Promise(function (resolve, reject) {
const url = config.serviceNowEndpoint + config.serviceNowChangeUri;
request.post({
url: url,
method: 'POST',
headers: headers,
json: payload
}, function (error, response, body) {
if (!error) {
return resolve(body.change_request.number);
}
else {
return reject(new Error('Returned with status code: ' + response.statusCode));
}
});
});
}
More Edit: As per #Jaromanda X's suggestion, modified the code to:
module.exports = function (payload) {
var headers = config.Headers;
return decrypt(headers.Authorization)
.then(function (auth_token) {
headers.Authorization = 'Basic ' + auth_token;
console.log('dispatch got the key from kms');
return makePostCall(headers, payload);
})
.catch (function (error) {
console.error('Error during dispatch: ' + error);
return error;
});
};
Problem still persists though. Runs fine locally, but async on Lambda
Edit adding decrypt code:
const AWS = require('aws-sdk');
const config = require('../config/config');
module.exports = function(token) {
return new Promise(function (resolve, reject) {
const kms = new AWS.KMS({ region: config.aws_region.region});
const params = {
CiphertextBlob: new Buffer(token, 'base64')
};
kms.decrypt(params, function (err, data) {
if (!err) {
console.log('decrypted successfully');
return resolve(data.Plaintext.toString());
} else {
return reject(`${err.message}`);
}
});
});
};
That's an AWS lambda bug. See the issue at Bluebird's repo and basic reproducing code for the issue.
This issue is not actually related to bluebird but to asynchronous code handling and scheduling on lambda's deployment. https://github.com/nervous-systems/cljs-lambda/issues/62. Here is another issue that reproduces the bug: https://github.com/nervous-systems/cljs-lambda/issues/62.
A user raises a common issue:
Oh my god. Just in case anyone else lands here and makes my mistake: make sure you haven't accidentally deployed AWS.config.update(...) using a localhost endpoint instead of an actual AWS region-specific endpoint. That can present with similar symptoms as above (say, if the only async function you're using is accessing a DynamoDB document client) but for utterly different reasons.
Related
I am trying to issue an HTTP request to another web service, from a Google Cloud Function (GCF) that I have created. I need the HTTP request to complete and return that result inside of my GCF so that I can do something else with it.
My question is; What is the best way to use Promise inside a Google Cloud Function? Is what I am trying to do possible?
My code currently looks like this:
export const MyGCF = functions.https.onRequest((request, response) => {
let dayOfTheWeek: any;
const request1 = require('request');
const url = 'http://worldclockapi.com/api/json/pst/now';
function getDay() {
return new Promise((resolve, reject) => {
request1(url, { json: true }, (err: any, res: any, body: any) => {
if (err) {
return reject(err);
}
resolve(body.dayOfTheWeek);
});
});
}
getDay().then((data) => {
dayOfTheWeek = data;
console.log(dayOfTheWeek);
});
});
In general your approach will work, and you can define additional functions inside of your MyGCF handler, in the same way that you have defined getDay(). One problem with you current code however is that you're forgetting to "write a response" for the request being processed by MyGCF.
You can write a response for the request by calling send() on the second res argument of your MyGCF request handler. A simple example would be:
/* Sends a response of "hello" for the request */
res.send("hello");
With respect to your code, you can use res.send() in your .then() callback to send a response back to the client after getDay() has completed (see code below). Note also to include a .catch() clause and callback for the error case (with an error status) to ensure the client receives an appropriate error response if the call to getDay() fails:
export const MyGCF = functions.https.onRequest((req, res) => {
const request = require('request');
const url = 'http://worldclockapi.com/api/json/pst/now';
function getDay() {
return new Promise((resolve, reject) => {
request(url, {
json: true
}, (err: any, r: any, body: any) => {
if (err) {
reject(err);
} else {
resolve(body.dayOfTheWeek);
}
});
});
}
getDay().then((dayOfTheWeek) => {
/* Send a response once the getDay() request complete */
res.send(dayOfTheWeek);
})
.catch(err => {
/* Don't forget the error case */
res.status(500).send(err);
});
});
I'm pulling an Excel file from an API, and need to read and modify it using the xlsx library. I'm using node 8.10 and async/await for this:
const report = await getVisitorReport(visitorQueryString, 'test12.xls', uri);
let workbook = XLSX.readFile('test12.xls');
And here's the function getVisitorReport. Notice that I resolve the promise on finish for the pipe:
async function getVisitorReport(queryString, reportPath, uri) {
return new Promise((resolve, reject) => {
request({
url: uri,
qs: queryString,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Basic ' + new Buffer(process.env.username + ':' + process.env.password, 'utf8').toString('base64')
}
}, (error, response, body) => {
if (error) {
reject(error);
} else {
if (response.statusCode === 200) {
resolve(body);
} else if (response.statusCode === 409) {
setTimeout(() => {
resolve(getVisitorReport(queryString));
}, response.headers['Retry-After'] * 1000);
} else {
reject(response);
}
}
}).pipe(fs.createWriteStream(reportPath)).on('finish', resolve(reportPath));
});
}
The file is pulled and created correctly. It seems that the second line XLSX.readFile('test12.xls') happens before the file is done being saved locally. What am I doing wrong here? How do I make sure the file is saved before I try to read it? Why is .pipe.on('finish', resolve) not accomplishing this?
Thanks for the help!
The code immediately calls resolve here: .on('finish', resolve(reportPath)).
You can either provide the resolve function directly to the on handler :
.on('finish', resolve)
Or if you need to pass some data, use an arrow function:
.on('finish', () => resolve(args))
edit:
if you do it like this: .on('finish', resolve(reportPath)) it roughly equals to
resolve(reportPath); // immediately calls resolve which makes a promise resolved
// so await doesn't stop the further code execution
....on('finish', undefined)
To better understand look at this example:
const [bad, good] = document.querySelectorAll("button");
bad.addEventListener("click", console.log("I log immediately when the page loads and not at click event"))
good.addEventListener("click", () => console.log("I log properly when button is clicked"))
<button>broken button</button>
<button>good button</button>
I am using a promise to get my response.
but he value is not coming in my console.
can you tell me whats the problem.
providing my code below
const https = require('https');
/*
* Complete the function below.
* Use console.log to print the result, you should not return from the function.
*/
function getMovieTitles(substr) {
return new Promise((resolve,reject) => {
https.get('https://jsonmock.hackerrank.com/api/movies/search/?Title=spiderman&page=1', (res) =>{
// var {statusCode} = res;
//var contentType = res.headers['content-type'];
console.log('res---->', res);
})
})
}
Here you go,
new Promise(function(resolve, reject) {
https.get('your_url_here', (res) =>{
// var {statusCode} = res;
// var contentType = res.headers['content-type'];
resolve(res);
})
}).then(function(res){
console.log("Response",res);
})
To Test Please Follow - https://jsfiddle.net/qdjkreo5/3741/
Alternatives :
You can deal with the data returned by https module in its callback itself as https also returns a callback.
const https = require('https');
https.get('https://encrypted.google.com/', (res) => {
console.log('response', res);
}).on('error', (e) => {
console.error(e);
});
Apart from that To Combine REST API calls with JavaScript Promises in node.js you can make use of the npm module request-promise.
var request = require('request-promise');
request({
"method":"GET",
"uri": "https://api.github.com/",
"json": true,
"headers": {
"User-Agent": "My little demo app"
}
}).then(console.log, console.log);
Building on the prior answers, here's a promise framework that handles both resolve (response, but not necessarily 200) and reject (no response). The axios package does this but hides the details. I like this a little better because it gives me more control over the logic.
const p1 = new Promise((resolve, reject) => {
https.get(url, (res) => {
let {statusCode} = res;
let contentType = res.headers['content-type'];
resolve(`status=${statusCode} type=${contentType}`); // response, so resolve
}).on('error', (err) => {
reject(err); // no response, so reject
});
});
p1.then(res => console.log(`${url}: ${res}`)) // resolve
.catch(err => console.log(err)) // reject
.finally(console.log('Finally')); // always
Test on the following URLs,
https://google.com
https://x.com
http://x.com
I'm trying to learn Node.js via Express.js framework. Currently I need to make an API call to get some data usefull for my app.
The API call is made with Request middleware, but when I'm out of the request my variable become undefined ... Let me show you :
var request = require('request');
var apiKey = "FOOFOO-FOOFOO-FOO-FOO-FOOFOO-FOOFOO";
var characters = [];
var gw2data;
var i = 0;
module.exports.account = function() {
request('https://api.guildwars2.com/v2/characters/?access_token=' + apiKey, function (error, response, body) {
gw2data = JSON.parse(body);
console.log('out request ' + gw2data); // {name1, name2 ...}
for (i; i < gw2data.length; i++) {
getCharacInfo(gw2data[i], i);
}
});
console.log('out request ' + characters); // undefined
return characters;
};
function getCharacInfo (name, position) {
request('https://api.guildwars2.com/v2/characters/' + name + '/?access_token=' + apiKey, function (error, response, body) {
if (!error && response.statusCode == 200) {
characters[position] = JSON.parse(body);
}
});
}
I don't understand why the gw2data variable become undefined when I go out of the request ... Someone can explain me ?
EDIT : I come to you because my problem has changed, I now need to make an API call loop in my first API call, same async problem I guess.
The previous code has evoluate with previous answers :
module.exports.account = function(cb) {
var request = require('request');
var apiKey = "FOOFOO-FOOFOO-FOO-FOO-FOOFOO-FOOFOO";
var characters = [];
var i = 0;
request('https://api.guildwars2.com/v2/characters/?access_token=' + apiKey, function(err, res, body) {
var numCompletedCalls = 1;
for (i; i < JSON.parse(body).length; i++) {
if (numCompletedCalls == JSON.parse(body).length) {
try {
console.log(characters); // {} empty array
return cb(null, characters);
} catch (e) {
return cb(e);
}
}else {
getCharacInfo(JSON.parse(body)[i]);
}
numCompletedCalls++;
}
});
};
function getCharacInfo (name) {
request('https://api.guildwars2.com/v2/characters/' + name + '/?access_token=' + apiKey, function (err, res, body) {
if (!err) {
console.log(characters); // {data ...}
characters.push(JSON.parse(body));
}
});
}
And the call of this function :
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
var charactersInfos = require('../models/account');
charactersInfos.account(function(err, gw2data) {
if (err) {
console.error('request failed:', err.message);
} else {
res.render('index', {
persos : gw2data
});
}
});
});
The problem is, when I return the characters array, it's always an empty array, but when I check in my function getCharacInfo, the array contains the data ...
The reason gw2data is undefined in the second console.log is because your logging it too early. request is an asynchronous operation therefore it will return immediately...however, that doesn't mean the callback will.
So basically what your doing is logging gw2data before it's actually been set. When dealing with asynchronous operations the best approach is to generally make your own method asynchronous as well which can be accomplished in a couple of ways - the simplest being having your function accept a callback which expects the data in an asynchronous way e.g.
module.exports.account = function(cb) {
request(..., function(err, res, body) {
// return an error if `request` fails
if (err) return cb(err);
try {
return cb(null, JSON.parse(body));
} catch (e) {
// return an error if `JSON.parse` fails
return cb(e);
}
});
}
...
var myModule = require('mymodule');
myModule.account(function(err, g2wdata) {
if (err) {
console.error('request failed', err.message);
} else {
console.log('out request', g2wdata);
}
});
Based on your syntax I'm assuming you aren't working with ES6, worth looking at this (particularly if your just starting to learn). With built-in promises & also async-await support coming relatively soon this sort of stuff becomes relatively straightforward (at least at the calling end) e.g.
export default class MyModule {
account() {
// return a promise to the caller
return new Promise((resolve, reject) => {
// invoke request the same way but this time resolve/reject the promise
request(..., function(err, res, body) {
if (err) return reject(err);
try {
return resolve(JSON.parse(body));
} catch (e) {
return reject(e);
}
});
});
}
}
...
import myModule from 'mymodule';
// handle using promises
myModule.account()
.then(gw2data => console.log('out request', gw2data))
.catch(e => console.error('request failed', e));
// handle using ES7 async/await
// NOTE - self-executing function wrapper required for async support if using at top level,
// if using inside another function you can just mark that function as async instead
(async () => {
try {
const gw2data = await myModule.account();
console.log('out request', gw2data);
} catch (e) {
console.log('request failed', e);
}
})();
Finally, should you do decide to go down the Promise route, there are a couple of libraries out there that can polyfill Promise support into existing libraries. One example I can think of is Bluebird, which from experience I know works with request. Combine that with ES6 you get a pretty neat developer experience.
The request API is asynchronous, as is pretty much all I/O in Node.js. Check out the Promise API which is IMO the simplest way to write async code. There are a few Promise adapters for request, or you could use something like superagent with promise support.
I am creating a function in Node js Express, that would be called by clients to download content.
The content needs to be downloaded from disparate sources and response needs to be send back only when all downloads have completed (content downloaded by node is zipped and send back to the calling client). So, all the download functions are wrapped in Promise.all(download1(), download2(), download3())
One of the download functions not only downloads content but it also generates a json and sends it back to the main function. The main function sets it as a response header.
The API function called by client looks like this
function downloadAll(request, response) {
// Download content only after folders are created
return createDirPromise.then(function (result) {
var statusJson = ... // somehow get the status json from download1() so that it can be send to the client
return Promise.all([download1(contentsJson), download2(contentsJson), download3(contentsJson)]);
})
.then(function (result) {
return new Promise(function (resolve, reject) {
// Create zip. Here is the zip creation logic.
// Once zip is created, send it to client as a buffer
zipdir(zipFolderPath, function (err, buffer) {
if (err) {
console.log('Error while zipping!!! '+JSON.stringify(err));
return reject({ data: null, resp_status_code: 500, resp_status_message: JSON.stringify(err)});
} }
console.log('Zipping successful');
return resolve(
{
data: buffer,
resp_status_code: 200,
resp_status_message: "Zip succeeded",
headers: statusJson
});
})
})
.catch(function (error) {
return {
data: 'Error in any of above ' + error,
resp_status_code: 500,
resp_status_message: 'Error while zipping'
}
}
This is how download1 function looks like
function download1(contentsJson) {
return new Promise(function(resolve, reject) {
//set the status json here
var statusJson = { "key1": "value1", "key2": "value2"};
//do the download stuff here and return the statusJson
console.log('Downloading complete, return the status so that it can be send to the client');
resolve(statusJson);
}
I know how to send the headers back to the client. My challenge is how to get the statusJson in the downloadAll function. download1 function is called from within Promise.all(). As soon as download1, download2 and donwload3 complete '.then' is executed. I am not able to find a way to get the data (statusJson) returned by donwload1 inside downloadAll.
I am not able to find a way to get the data (statusJson) returned by donwload1 inside downloadAll.
It's the first entry in the array that you receive as result in your then callback on the Promise.all():
function downloadAll(request, response) {
return createDirPromise.then(function (result) {
return Promise.all([download1(contentsJson), download2(contentsJson), download3(contentsJson)]);
})
.then(function (result)
// **** Here, the result of download1 is result[0]
var statusJson = result[0];
Promise.all gathers together the promise results and returns them in an array in the order you gave it the promises.
(Side note: I think you're missing () after createDirPromise.)
Example:
// (Requires Promise support in your browser)
"use strict";
var createDirPromise = createPromiseFunction("createDirPromise");
var download1 = createPromiseFunction("download1");
var download2 = createPromiseFunction("download2");
var download3 = createPromiseFunction("download3");
function downloadAll(request, response) {
return createDirPromise().then(function(result) {
return Promise.all([download1(/*contentsJson*/), download2(/*contentsJson*/), download3(/*contentsJson*/)]);
})
.then(function(result) {
// **** Here, the result of download1 is result[0]
var statusJson = result[0];
console.log("statusJson = '" + statusJson + "'");
});
}
downloadAll();
function createPromiseFunction(name) {
return function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("resolving " + name);
resolve("result of " + name);
}, Math.random() * 50);
});
};
}