I'm trying to make REST apis with the serverless framework.
Some of the functions are asynchronous.
So I'm using Promise.
But the promise is not working (no response)
So, I'm using the await keyword. It works fine.
I think this is bad way. How to use promise in serverless framework?
Any advice or suggestion would be appreciated. Thank you in advance.
You can use the promise of many ways. Personally, separate the promise in another function.
I made a example with request module:
const request = require("request");
// The promise
const requestPromise = (url, options) =>
new Promise((resolve, reject) => {
options = options || {};
const processRequest = (err, response) => (err ? reject(err) : resolve(response));
request(url, options, processRequest);
});
// You can use like this
module.exports = (event,context) => {
let url = event.url;
requestPromise(url)
.then(response => {
// Do something
context.succeed({succeed: true /* put return data here */})
})
.catch(details => context.fail({error: true, details: details}));
}
// Or this
module.exports = async (event,context) => {
try {
let url = event.url;
let response = await requestPromise(url);
// Do something
context.succeed({succeed: true /* put return data here */});
} catch (details) {
context.fail({error: true, details: details});
}
}
If you use async/wait, you need add try/catch to handler errors.
I am coding a serverless-kubeless api now for the mysql world database. I had to solve this problem yesterday. I arrived at the following solution. It's not feature complete. But you didn't ask for that. So here is a working GET endpoint which accepts various query parameters to customise the query.
'use strict';
const pool = require('./database');
module.exports.handler = async (event, context) => new Promise((resolve, reject) => {
let request = event.extensions.request;
let response = event.extensions.response;
try{
let handleResults = (err, results, fields) => {
if(err){
response.status(500).send({
success: false,
message: err.message,
});
}else{
response.status(200).send({
success: true,
count: results.length,
data: results,
});
}
}
if(typeof(request.query.id) !== "undefined"){
// search for a specific region by id
if (Number.isNaN(Number(request.query.id))) {
response.status(500).send({
success: false,
message: "id query param was not a number",
});
}
pool.query("select id,name,code,country_id from regions where id = ?", [request.query.id], handleResults);
}else if(typeof(request.query.country) !== "undefined"){
// search for a region list from a specific country
if (Number.isNaN(Number(request.query.country))) {
response.status(500).send({
success: false,
message: "country query param was not a number",
});
}
pool.query("select id,name,code,country_id from regions where country_id = ?", [request.query.country], handleResults);
}else{
response.status(400).send({
success: false,
message: "Could not find country, or region query parameter. Require a search term"
});
}
}catch(exception){
response.status(500).send({
success: false,
message: exception.message
});
}
});
and database.js:
const mysql = require("mysql");
const util = require('util');
const pool = mysql.createPool({
connectionLimit: 10,
host: process.env.DATABASE_HOSTNAME,
user: process.env.DATABASE_USERNAME,
port: process.env.DATABASE_PORT,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
});
pool.getConnection((err, connection) => {
if (err) {
if (err.code === 'PROTOCOL_CONNECTION_LOST') {
console.error('Database connection was closed.');
}
if (err.code === 'ER_CON_COUNT_ERROR') {
console.error('Database has too many connections.');
}
if (err.code === 'ECONNREFUSED') {
console.error('Database connection was refused.');
}
}
if (connection) connection.release();
return;
});
// Magic happens here.
pool.query = util.promisify(pool.query);
module.exports = pool;
I commonly do stuff with Promises in my serverless projects:
//this would me in a module like: services/myhttpservice.js (for example)
//wrap the GET HTTP request in a Promise
module.exports.GetUrlPromise = function(url, cookie_session_value) {
console.log(new Date().getTime() + " GetUrlPromise() CALLED: " + url);
var j = request.jar();
if(cookie_session_value){
var cookie1 = request.cookie(cookie_name + '=' + cookie_session_value);
j.setCookie(cookie1, cookie_domain);// domain used by the cookie, maybe make more generic?
}
// create the "Basic" auth header
var auth = "Basic " + Buffer.from(basic_username + ":" + basic_password).toString("base64");
//create request options
var options = {
'method': 'GET',
'url': url,
'jar': j,
'headers': {
'Authorization': auth,// set Basic auth header that is the base64 of the un:pw combo
'Content-Type': 'application/json'
}
};
return new Promise((resolve, reject) => {
request(options, function (error, response, body) {
if(error){
console.log('error:', error);
reject(error);
}else{
console.log('statusCode:', response && response.statusCode);
// object for returning response results
var http_resp = {};
http_resp._session = GetCookieValue(response);
http_resp.body = body;
http_resp.statusCode = response.statusCode;
//http_resp.response = response;
http_resp.requestType = 'GET';
console.log(JSON.stringify(http_resp));
resolve(http_resp);
}
});
});
}
It gives me the ability to make promised calls to my services easily:
//in my controller code:
myhttpservice.GetUrlPromise(page_url, user_session)
.then((http_resp)=>{ etc...
Await and async are not bad practices if used correctly.
If you don't have promises depending on each other you can call them in 'parallel' by adding all promises (without await) in an array and use const responses = await Promise.all(promisesArray) to wait for all responses to be successful.
For more information refer to this answer which explains very well Call async/await functions in parallel
Related
I'm new to Serverless with NodeJS, I'm trying to use an Async function to hash a password and perform some stuff in a database, the problem is that when I declare the functions async I always get this error:
But if I remove the async keyword here:
module.exports.login = async (event, context, callback) => {
the function runs properly, but of course I won't be able to use promises within the function.
This is an endpoint, to make API calls.
Here's my code:
'use strict';
require('dotenv').config({ path: './.env' });
const dataBase = require('./utils/db');
const bcrypt = require('bcrypt');
const encryptPassword = async (plainPassword) => {
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);
return hashedPassword;
};
module.exports.login = async (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const parsedBody = JSON.parse(event.body);
const connect = dataBase.connectToDatabase();
const newPassword = await encryptPassword('test');
console.log(newPassword);
connect.query('SELECT * FROM users', (error, results, fields) => {
if (error) {
console.error(error);
callback(null, {
statusCode: 500,
body: JSON.stringify({
error: JSON.stringify(error),
message: 'Internal Server Error Booh!',
}),
});
}
if (results) {
callback(null, {
statusCode: 200,
body: JSON.stringify({
error: results,
message: 'No Error',
}),
});
}
});
};
Here my db connection and config:
db.js
const mysql = require('mysql');
const isDev = true;
// Create mySQL Connection
const connectToDatabase = () => {
const pool = mysql.createPool({
host: isDev ? process.env.DATABASE_HOST_DEV : process.env.DATABASE_HOST_PROD,
user: isDev ? process.env.DATABASE_USER_DEV : DATABASE_USER_PROD,
password: isDev ? process.env.DATABASE_PASSWORD_DEV : DATABASE_PASSWORD_PROD,
database: isDev ? process.env.DATABASE_DATABASE_DEV : DATABASE_DATABASE_PROD,
multipleStatements: true,
});
return pool;
};
exports.connectToDatabase = connectToDatabase;
exports.mysql = mysql;
What am I missing?
EDIT:
The purpose of all this, it's because I'm learning serverless.
All this will end up in an AWS Lambda with an endpoint using API Gateway.
So, when you call this endpoint, you will be sending some params to register/login into an app.
I need to use async/await because if you register an account, the password needs to be hashed and then store into a database or if you login, the password will need to be compared, both of these actions are asynchronous ones.
That's why I need the endpoint to be an async function.
EDIT 2:
Reading this post: https://github.com/netlify/netlify-dev-plugin/issues/160
As Phil mention, async and callback shouldn't be used togehter, so I modified my code like this, and same error:
'use strict';
require('dotenv').config({ path: './.env' });
const dataBase = require('./utils/db');
const bcrypt = require('bcrypt');
const encryptPassword = async (plainPassword) => {
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);
return hashedPassword;
};
module.exports.login = async (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const parsedBody = JSON.parse(event.body);
const connect = dataBase.connectToDatabase();
const newPassword = await encryptPassword('test');
console.log(newPassword);
connect.query('SELECT * FROM users', (error, results, fields) => {
if (error) {
console.error(error);
return {
error: 'some error to test',
};
}
if (results) {
return {
body: 'someBody',
};
}
});
};
FINAL FUNCTIONAL CODE:
In case any wonder how to make these queries as promises, here's my approach, hope it helps anyone out there struggling with the same.
'use strict';
require('dotenv').config({ path: './.env' });
const dataBase = require('./utils/db');
const bcrypt = require('bcrypt');
const encryptPassword = async (plainPassword) => {
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);
return hashedPassword;
};
module.exports.login = async (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const parsedBody = JSON.parse(event.body);
const connect = dataBase.connectToDatabase();
const hashedPassword = await encryptPassword('test');
console.log(hashedPassword);
return new Promise((resolve, reject) => {
connect.query('SELECT * FROM users', (error, result) => {
if (error) {
reject(
callback(null, {
statusCode: 500,
body: JSON.stringify({
error: JSON.stringify(error),
message: 'Internal Server Error Booh!',
}),
})
);
}
if (result) {
resolve(
callback(null, {
statusCode: 200,
body: JSON.stringify({
data: result,
message: 'No Error',
}),
})
);
}
});
});
};
Regards.
Taking a quick look at the docs, it doesn't look like you should be mixing async with the callback arg. Just use one or the other, not both.
You could migrate to the mysql2 library so you can use promises or stick with the one you've got and use encryptPassword("test").then() instead of await
// no async
module.exports.login = (event, context, callback) => {
// remove this, you don't want it
// context.callbackWaitsForEmptyEventLoop = false;
const parsedBody = JSON.parse(event.body);
const connect = dataBase.connectToDatabase();
encryptPassword("test").then(newPassword => { // use .then()
console.log(newPassword);
// callback APIs are a pain, recommend migrating to mysql2 and use promises
connect.query('SELECT * FROM users', (error, results, fields) => {
if (error) {
console.error(error);
return callback(null, {
statusCode: 500,
body: JSON.stringify({
error: JSON.stringify(error),
message: 'Internal Server Error Booh!',
}),
});
}
callback(null, {
statusCode: 200,
body: JSON.stringify({
error: results, // error?
message: 'No Error',
}),
});
}
});
}
I have a piece of code where I am trying to get the response from Okta to a variable and return it to the calling service.
I am trying to use async also along with this. But this is keep getting failed like response from the post request is never coming to the try block. How can I achive this?
exports.oktaLogin = async function (request) {
//const transaction = await sequelizedb.transaction();
logger.info('UserServices.oktaLogin',request);
let headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
let username = request.username;
let password = request.password;
let dataString = "{\"username\": \""+username+"\", \"password\": \""+password+"\", \"options\": { \"multiOptionalFactorEnroll\": true, \"warnBeforePasswordExpired\": true } }";
//console.log(dataString);
options = {
url: constants.OKTA_URL,
method: 'POST',
headers: headers,
body: dataString
};
function callback(error, response, body) {
//console.log('Called Call back');
if (!error && response.statusCode == 200) {
console.log('Inside call back');
console.log(body);
if(body){
return { success: true, user: body };
}else{
return false;
}
}else{
return { success: false, error: 'No User Found' };
}
}
try {
let some = await reqcall(options, callback);
console.log('--------------------------')
console.log(some);
} catch (error) {
return { success: false, error: 'No User Found' };
}
};
How do I make the call back to work with async?
Also is there a possibility to use this With axios?
with axios, I would write something like
const axios = require('axios');
exports.oktaLogin = async (req) => {
const { username, password } = req;
const payload = {
username, password,
options: { multiOptionalFactorEnroll: true, warnBeforePasswordExpired: true }
};
try {
const res = await axios.post(constants.OKTA_URL, payload);
return {
success: true,
user: res.data
};
} catch (err) {
return {
success: false,
error: 'No User Found',
message: err.message
};
}
};
that matchs what you have, but I would do a bit more, for example, I would not rely on success but would send back an error for example
const axios = require('axios');
exports.oktaLogin = async (username, password) => {
const payload = {
username, password,
options: { multiOptionalFactorEnroll: true, warnBeforePasswordExpired: true }
};
return await axios.post(constants.OKTA_URL, payload);
};
the function will now return what it should, and you know exactly what parameters such function needs (username and password, and not the ExpressJs full request object as you don't need and looking at the function signature it's hard to understand what you will need that object to have)
then I'd use as
app.post('/login', async (req, res) => {
const { username, password } = req;
if (!isUsernameValid(username) || !isPasswordValid(username)) {
res.status(400).json({ error: 'Invalid input' });
}
try {
const login = await oktaLogin(username, password);
res.json({
user: login.data
});
} catch (err) {
res.status(400).json({ error: err.message });
}
});
where, just as an example
const isPasswordValid = password => password.length > 8;
const isUsernameValid = username => username.length > 3;
you could read a bit more about axios and create your own javascript file to test what it returns and what it gets, a simple file with
const axios = require('axios');
(async() => {
const HOST = 'https://jsonplaceholder.typicode.com';
const api = axios.create({
baseURL: HOST
});
// get first TODO
let res = await api.get('/todos/1');
console.log(res.status, JSON.stringify(res.data, null, 2));
// create new TODO
res = await api.post('/todos', {
title: 'test title',
body: 'test body'
});
const newId = res.data.id;
console.log(res.status, JSON.stringify(res.data, null, 2));
// delete last todo
res = await api.delete(`/todos/${newId}`);
console.log(res.status, JSON.stringify(res.data, null, 2));
})();
and try other calls, use a REST service for example, like https://jsonplaceholder.typicode.com/ so you can use other HTTP verbs
and run as node ./index.js (if the new file is index.js
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
In that code, console.log(game) gives me an array, but return(game) gives me null.
I don't know, what should I do to gain that array
takeApi: async (root, args, { req }, info) =>{
let userNick='Izyi';
var request = require('request');
var JsonFind=require('json-find');
var url = 'https://someapi/'+userNick;
var game;
request.get({
url: url,
json: true,
headers: {'API-KEY': 'XXX'}
}, (err, res, data) => {
if (err) {
console.log('Error:', err);
} else if (res.statusCode !== 200) {
console.log('Status:', res.statusCode);
} else {
const doc= JsonFind(data.lifeTimeStats);
var matchesPlayed=(doc.checkKey('7').value);
var wins=(doc.checkKey('8').value);
var kills=(doc.checkKey('10').value);
game ={kills:kills,wins:wins,matchesPlayed:matchesPlayed}
console.log(game);
return(game);
}
})
return(game);
}
request.get works via a callback and is not directly compatible with async/await. That callback happens when the request is done or has errored out. The return(game); then happens before the request has completed.
You need to return a new Promise and then resovle or reject based on the results passed to the callback.
You can then await or .then takeApi and expect to have a value returned.
const takeApi = async(root, args, { req }, info) => {
let userNick = 'Izyi';
var request = require('request');
var JsonFind = require('json-find');
var url = 'https://someapi/' + userNick;
// return a Promise, which will work
// by the called using `await` or `.then`
return new Promise((resolve, reject) => {
request.get({
url: url,
json: true,
headers: {
'API-KEY': 'XXX'
}
}, (err, res, data) => {
if (err) {
console.log('Error:', err);
// error, reject
reject(err);
} else if (res.statusCode !== 200) {
console.log('Status:', res.statusCode);
// error, reject
reject(res.statusCode);
} else {
const doc = JsonFind(data.lifeTimeStats);
var matchesPlayed = (doc.checkKey('7').value);
var wins = (doc.checkKey('8').value);
var kills = (doc.checkKey('10').value);
const game = {
kills: kills,
wins: wins,
matchesPlayed: matchesPlayed
}
console.log(game);
// success, resolve
resolve(game);
}
})
});
}
I want to do make the same api call as made in this postman photo below :
postman
I have already tried the following code but it only returns some html not the desired response
async function vendor_info() {
const options = {
uri: 'http://**.**.**.**/vendor/:username/pj1527',
json: true,
method: 'GET'
};
let vendor_info = undefined;
await requestPromise(options)
.then((body) => {
// err_token = 0;
vendor_info = body[0];
console.log(body);
// console.log(body[0]);
})
.catch(err => {
vendor_info = undefined;
// err_token = 1;
// console.log(err);
});
return vendor_info;
}
EDIT
It's working now, actually the url should be 'http://.../vendor//pj1527' in the request.
If you are using async await then there is no need to use then and catch blocks, Try like this:
async function vendor_info() {
try {
const options = {
uri: 'http://**.**.**.**/vendor/:username/pj1527',
json: true,
method: 'GET'
};
const vendor_info = await requestPromise(options);
console.log('vendor_info: => ', vendor_info);
return vendor_info;
} catch (err) {
console.log('Error: => ', err);
return err;
}
}
Hope this helps :)
I'm trying to wrap http.request into Promise:
new Promise(function(resolve, reject) {
var req = http.request({
host: '127.0.0.1',
port: 4000,
method: 'GET',
path: '/api/v1/service'
}, function(res) {
if (res.statusCode < 200 || res.statusCode >= 300) {
// First reject
reject(new Error('statusCode=' + res.statusCode));
return;
}
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
res.on('end', function() {
try {
body = JSON.parse(Buffer.concat(body).toString());
} catch(e) {
reject(e);
return;
}
resolve(body);
});
});
req.on('error', function(err) {
// Second reject
reject(err);
});
req.write('test');
}).then(function(data) {
console.log(data);
}).catch(function(err) {
console.log(err);
});
If I recieve errornous statusCode from remote server it will call First reject and after a bit of time Second reject. How to make properly so it calls only single reject (I think First reject is proper one in this case)? I think I need to close res myself, but there is no close() method on ClientResponse object.
UPD:
Second reject triggers very rarely - why?
Your code is almost fine. To restate a little, you want a function that wraps http.request with this form:
function httpRequest(params, postData) {
return new Promise(function(resolve, reject) {
var req = http.request(params, function(res) {
// on bad status, reject
// on response data, cumulate it
// on end, parse and resolve
});
// on request error, reject
// if there's post data, write it to the request
// important: end the request req.end()
});
}
Notice the addition of params and postData so this can be used as a general purpose request. And notice the last line req.end() -- which must always be called -- was missing from the OP code.
Applying those couple changes to the OP code...
function httpRequest(params, postData) {
return new Promise(function(resolve, reject) {
var req = http.request(params, function(res) {
// reject on bad status
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error('statusCode=' + res.statusCode));
}
// cumulate data
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
// resolve on end
res.on('end', function() {
try {
body = JSON.parse(Buffer.concat(body).toString());
} catch(e) {
reject(e);
}
resolve(body);
});
});
// reject on request error
req.on('error', function(err) {
// This is not a "Second reject", just a different sort of failure
reject(err);
});
if (postData) {
req.write(postData);
}
// IMPORTANT
req.end();
});
}
This is untested, but it should work fine...
var params = {
host: '127.0.0.1',
port: 4000,
method: 'GET',
path: '/api/v1/service'
};
// this is a get, so there's no post data
httpRequest(params).then(function(body) {
console.log(body);
});
And these promises can be chained, too...
httpRequest(params).then(function(body) {
console.log(body);
return httpRequest(otherParams);
}).then(function(body) {
console.log(body);
// and so on
});
I know this question is old but the answer actually inspired me to write a modern version of a lightweight promisified HTTP client. Here is a new version that:
Use up to date JavaScript syntax
Validate input
Support multiple methods
Is easy to extend for HTTPS support
Will let the client decide on how to deal with response codes
Will also let the client decide on how to deal with non-JSON bodies
Code below:
function httpRequest(method, url, body = null) {
if (!['get', 'post', 'head'].includes(method)) {
throw new Error(`Invalid method: ${method}`);
}
let urlObject;
try {
urlObject = new URL(url);
} catch (error) {
throw new Error(`Invalid url ${url}`);
}
if (body && method !== 'post') {
throw new Error(`Invalid use of the body parameter while using the ${method.toUpperCase()} method.`);
}
let options = {
method: method.toUpperCase(),
hostname: urlObject.hostname,
port: urlObject.port,
path: urlObject.pathname
};
if (body) {
options.headers = {'Content-Length':Buffer.byteLength(body)};
}
return new Promise((resolve, reject) => {
const clientRequest = http.request(options, incomingMessage => {
// Response object.
let response = {
statusCode: incomingMessage.statusCode,
headers: incomingMessage.headers,
body: []
};
// Collect response body data.
incomingMessage.on('data', chunk => {
response.body.push(chunk);
});
// Resolve on end.
incomingMessage.on('end', () => {
if (response.body.length) {
response.body = response.body.join();
try {
response.body = JSON.parse(response.body);
} catch (error) {
// Silently fail if response is not JSON.
}
}
resolve(response);
});
});
// Reject on request error.
clientRequest.on('error', error => {
reject(error);
});
// Write request body if present.
if (body) {
clientRequest.write(body);
}
// Close HTTP connection.
clientRequest.end();
});
}
There are other ways as well but here you can find a simple way to make http.request as a promise or async/await type.
Here is a working sample code:
var http = require('http');
function requestAsync(name) {
return new Promise((resolve, reject) => {
var post_options = {
host: 'restcountries.eu',
port: '80',
path: `/rest/v2/name/${name}`,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};
let post_req = http.request(post_options, (res) => {
res.setEncoding('utf8');
res.on('data', (chunk) => {
resolve(chunk);
});
res.on("error", (err) => {
reject(err);
});
});
post_req.write('test');
post_req.end();
});
}
//Calling request function
//:1- as promise
requestAsync("india").then(countryDetails => {
console.log(countryDetails);
}).catch((err) => {
console.log(err);
});
//:2- as await
let countryDetails = await requestAsync("india");
After reading all of these and a few articles, I thought I'd post a sort of "general" solution that handles both http and https:
const http = require("http");
const https = require("https");
const url_obj = require("url");
const request = async (url_string, method = "GET", postData = null) => {
const url = url_obj.parse(url_string);
const lib = url.protocol=="https:" ? https : http;
const params = {
method:method,
host:url.host,
port: url.port || url.protocol=="https:" ? 443 : 80,
path: url.path || "/"
};
return new Promise((resolve, reject) => {
const req = lib.request(params, res => {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error(`Status Code: ${res.statusCode}`));
}
const data = [];
res.on("data", chunk => {
data.push(chunk);
});
res.on("end", () => resolve(Buffer.concat(data).toString()));
});
req.on("error", reject);
if (postData) {
req.write(postData);
}
req.end();
});
}
You could use like this:
request("google.com").then(res => console.log(res)).catch(err => console.log(err))
This is heavily inspired by this article, but replaces the hacky url parsing with the built in api.
Hope this help.
const request = require('request');
async function getRequest() {
const options = {
url: 'http://example.com',
headers: {
'Authorization': 'Bearer xxx'
}
};
return new Promise((resolve, reject) => {
return request(options, (error, response, body) => {
if (!error && response.statusCode == 200) {
const json = JSON.parse(body);
return resolve(json);
} else {
return reject(error);
}
});
})
}
It's easier for you to use bluebird api, you can promisify request module and use the request function async as a promise itself, or you have the option of using the module request-promise, that makes you to not working to creating a promise but using and object that already encapsulates the module using promise, here's an example:
var rp = require('request-promise');
rp({host: '127.0.0.1',
port: 4000,
method: 'GET',
path: '/api/v1/service'})
.then(function (parsedBody) {
// GET succeeded...
})
.catch(function (err) {
// GET failed...
});