5

Node.JS 10.15, serverless, lambdas, invoked locally

SAMPLE A) This Works:

export async function main(event) {
    const marketName = marketIdToNameMap[event.marketId];
    const marketObject = marketDirectory[marketName];
    const marketClient = await marketObject.fetchClient();

    const marketTime = await marketObject.getTime(marketClient);
    console.log(marketTime);
}

SAMPLE B) and this works:

export function main(event) {
    const marketName = marketIdToNameMap[event.marketId];
    const marketObject = marketDirectory[marketName];

    marketObject.fetchClient().then((marketClient)=>{
        marketObject.getTime(marketClient).then((result) => {
            console.log('<---------------> marker 1 <--------------->');
            console.log(result);
        });
    });
}

SAMPLE C) but this does not:

export async function main(event) {
    const marketName = marketIdToNameMap[event.marketId];
    const marketObject = marketDirectory[marketName];
    const marketClient = await marketObject.fetchClient();

    console.log('<---------------> marker 1 <--------------->');

    marketObject.getTime(marketClient).then((result) => {
        console.log('<---------------> marker 22 <--------------->');
        console.log(result);
    });
}

the guts of getTime are for all examples:

function getTime(marketClient){
    return new Promise((resolve, reject) => {
        return marketClient.getTime((err, result) => {
            if (err) {
                reject(err);
            }
            resolve(result);
        });
    }).catch(err => {
        throw err;
    });
}

clearly, it seems to be an issue with mixing async/awaits with classic promise then-ables. I would expect SAMPLE C to work because getTime() is returning a promise. However, the code simply finishes silently, never hitting the second marker. I have to put the first marker there just to be sure any code is run at all. It feels like i should be able to mix async/await and thenables, but i must not be considering something here.

@adrian, nope enter image description here

chazsolo
  • 7,873
  • 1
  • 20
  • 44
zentechinc
  • 369
  • 1
  • 5
  • 15
  • 1
    In sample B, are you actually _returning_ that continuation? By the way `.catch(err => { throw err; });` is a no-op, get rid of it. – Patrick Roberts May 07 '19 at 05:22
  • 1
    Can you provide how you call each of these? The problem is probably related to how / whether you are consuming the promises. – Patrick Roberts May 07 '19 at 05:26
  • you must be doing something else wrong, all three behave identically (even though B is not exactly how you'd do things - chain, not nest) – Jaromanda X May 07 '19 at 05:31
  • @PatrickRoberts hey dude, thanks for you comments, been finishing my sprint work today, so i havent had a chance to check in. Re: your first comment, I will eventually be doing work with the result from getTime, but am not currently in this sample. Mainly just wanna know why it's not behaving as expected. Re: your second comment, these functions are being initiated by aws events. Likely, the initial event that will be using these functions will be a cron hitting the lambda, but i also intend to use getTime to validate client-to-server connection in various other functions. Does that help? – zentechinc May 07 '19 at 22:57
  • @zentechinc [this](https://stackoverflow.com/a/56023044/1541563) precisely explains what I suspected the issue was in my second comment. I'm almost certain that's the correct answer. – Patrick Roberts May 07 '19 at 22:59
  • @JaromandaX, i agree re: sample B being awful, i will likely go with sample A, i'm just trying to figure why it's behaving contrary to how one might expect. – zentechinc May 07 '19 at 23:00
  • the code you posted, as posted, does not behave any differently in all 3 code blocks – Jaromanda X May 07 '19 at 23:21
  • So maybe something in the execution of the code with serverless local invoke, babel, etc? I'm trying to think of a good way to demonstrate the behavior without having to make GIFs of me running the code... You know what, that actually sounds kinda fun... – zentechinc May 08 '19 at 03:45
  • Just a sidenote: Using await has a big disadvantage. It do not return a Promise (as most people think). It returns an async function object that does not have the .catch or .finally methods of a real Promise. So most people leaves awaiting a sync function uncatched which is bad practise and can make the debugging much harder. (conclusion: NEVER let an async operation uncatched) – Kai Lehmann May 08 '19 at 07:52

1 Answers1

4

You're neither awaiting nor returning the promise from marketObject.getTime().then(), and this will result in that promise chain executing independently, the main function returning and the process closing. Remember.. then returns a promise too.

the solution is

await marketObject.getTime(marketClient).then(...

or

return marketObject.getTime(marketClient).then(...

either way chains the promise to the main function such that whatever executes it consistently waits for all promises to resolve (or reject).

I suspect Sample B works because the main is not async and Lambda will wait for the event-loop to complete. i.e. it will execute the promise chain even though main returned early.

https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html

If you don't use callback in your code, AWS Lambda will call it implicitly and the return value is null. When the callback is called, AWS Lambda continues the Lambda function invocation until the event loop is empty.

... and I suspect if you return a Promise (as you do in Sample C) then Lambda will simply terminate the process immediately once it resolves, which is does because you don't await/return the .then() chain, and thus the floating promise chain you've created will not execute.

Meirion Hughes
  • 24,994
  • 12
  • 71
  • 122
  • This sounds right. I'll have to test tomorrow evening then I'll tag you as correct if appropriate. That alright? – zentechinc May 08 '19 at 04:30
  • You appear correct, dude. Thanks for the good explanation. In my head, because i wasn't actually doing anything with the result from getTime at this point, and simply wanted to check whether my testClientConnection function (not shown in code) was doing what i expected by having it echo the client's time, i figured it would follow the promise path w/o needing the return keyword. Regardless, it still feels a little like it behaves in an unexpected fashion. the whole idea of the promise is that we're signalling for the code to evaluate a chain of potential async events. why require a return? – zentechinc May 09 '19 at 03:48
  • 1
    `function async main(){}` is basically syntax sugar to return a Promise. If you return another Promise within a `.then()` or in an async function, it will (as defined in the spec) automatically chain that promise for you. There is nothing wrong with mixing async with promises, but its important to ensure you link everything together, or you end up with detached promises on the event-loop. Sample B works only because Lambda is unsure if your method has finished (you don't use the callback). – Meirion Hughes May 09 '19 at 08:30