1

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.

squeekyDave
  • 918
  • 2
  • 16
  • 35
Hax0r
  • 1,722
  • 4
  • 25
  • 43
  • 1
    Think a bit on this: when (in time) is the request terminared and the response returned? And when is the promise resolved? Is there any other way to wait with the returning of the response until the promise is resolved? Your best choice is await. Why would this be bad? – ZorgoZ Dec 06 '18 at 06:35
  • @ZorgoZ Thanks to feedback. I think `await` have a performance issues. Suppose, have a very havy. the user always wait this job. right ? so i don't want it user is wait.. – Hax0r Dec 06 '18 at 06:42
  • 1
    The question lacks https://stackoverflow.com/help/mcve . It's unclear what the problem is. `async..await` is syntactic sugar for raw promises. – Estus Flask Dec 06 '18 at 06:43
  • 1
    No, await is rather not a performance issue, even more a gain. Yes, it is a syntactic sugar, but the alternate syntax in such a case is like hell: you still need to block the api method - and js has no tools for that. Webapis (in c# for example) are async since a few years. If you consider that it would take too long for the promise to complete, then webapi is not the best design choice: you can use websocket. Then, the call would be just the instruction to start performing the backend action. And the response could be pushed when you have it. – ZorgoZ Dec 06 '18 at 06:50
  • 1
    You're using the Promise wrong. Please show your code so we can find the problem. – Noel Llevares Dec 12 '18 at 02:50

4 Answers4

1

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.

Tiago Souza
  • 136
  • 2
  • 5
1

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;
Christopher Thomas
  • 4,424
  • 4
  • 34
  • 49
  • One comment and one question. Question - you never return a promise in the handler - how does that work? Comment - you should never, I mean never be writing code to return 500s. This means "Server Error" and it means that something went wrong inside the server (unhandled exception, out of memory, etc..) like really bad stuff. Both 500s above are due to the client passing incorrect data - I.E. Client error = 400. Often the default behavior of load balancers is to kill servers returning 500s, however this is serverless.. - so its likely not as big a deal. – TheJeff Jul 22 '19 at 21:08
  • 1
    regarding the 500's, yeah I probably should change that, regarding the promise. This sets the handler to an async function which when called, returns new Promise(...) – Christopher Thomas Jul 22 '19 at 22:03
1

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...
contractorwolf
  • 438
  • 1
  • 3
  • 14
1

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

Florin Mateescu
  • 197
  • 2
  • 12