Lamda function invocation ends before resolving the promise

Viewed 41

I have the following code to read a file from a URL and then upload it to another destination:

import request from 'request';
import FormData from 'form-data';

export const handler = async (event, context) => {
  new Promise((resolve, reject) => {
    const form = new FormData();
    console.log('Streaming ...');
    form.append('file', request('https://cdn.mysite.com/video.mp4'));
    console.log('Uploading ...');
    // Invocation ends here!
    form.submit('https://www.someapi.com/upload', (error, response) => {
      if (error) reject(error);
      let body = '';
      response.on('data', chunk => {
        console.log('Receiving response ...');
        body += chunk.toString()
      });
      response.on('end', () => {
        console.log('Done ...');
        resolve(JSON.parse(body))
      });
      response.resume();
    });
  });
};

Running this code locally works fine, but when I deploy and run it on AWS Lambda it ends before submitting the form. I tried to remove the Promise and run the code inside it but got the same result!

I don't know if it starts the submission or not, but the last thing I see on the cloud logs is Uploading ..., and the invocation ends after it immediately.

How to make the Lamda function waits until the promise is resolved?

2 Answers

This is missing a return statement as mentioned by Mark in the comments:

import request from 'request';
import FormData from 'form-data';

export const handler = async (event, context) => {
  // --- here, promises work with return values
  return new Promise((resolve, reject) => {
   // ... rest of your code
  });
};

But can be simplified extensively (assuming modern Node.js) without any nesting:

import request from 'request';
import FormData from 'form-data';
import { promisify } from 'util';

export const handler = async (event, context) => {
    const form = new FormData();
    console.log('Streaming ...');
    form.append('file', request('https://cdn.mysite.com/video.mp4'));
    console.log('Uploading ...');
    // Invocation ends here!
    await promisify(cb => form.submit('https://www.someapi.com/upload', cb))()
    const bodyParts = await response.toArray();
    return response.map(x => x.toString()).join('');
};

In general you almost never need to wrap things in new Promise in code.

I was able to resolve this by using a non-async handler, and removing the promise from the code. Here's a working copy of my code:

import request from 'request';
import FormData from 'form-data';

export const handler = (event, context, callback) => {
  const form = new FormData();
  console.log('Streaming ...');
  form.append('file', request('https://cdn.mysite.com/video.mp4'));
  console.log('Uploading ...');
  form.submit( 'https://www.someapi.com/upload', (error, response) => {
    if (error) throw error;
    let body = '';
    response.on('data', chunk => body += chunk.toString());
    response.on('end', () => {
      console.log('Done ...');
      callback(null, JSON.parse(body));
    });
    response.resume();
  });
};

In this case, the Lambda function will wait for the callback to terminate, based on the AWS Lambda function handler in Node.js documentation.

Update

Another solution using the https native module of Nodejs instead of the deprecated request library:

import https from 'https';
import FormData from 'form-data';

export const handler = (event, context, callback) => {
  const form = new FormData();
  console.log('Streaming ...');
  https.get('https://cdn.mysite.com/video.mp4', response => {
    form.append('file', response);
    console.log('Uploading ...');
    form.submit('https://www.someapi.com/upload', (error, response) => {
        if (error) callback(error);
        let body = '';
        response.on('data', chunk => body += chunk.toString());
        response.on('end', () => {
          console.log('Done ...');
          callback(null, JSON.parse(body));
        });
        response.resume();
      }
    );
  });
};
Related