I want to return a stream from a function for the main Lambda handler to create a pipe. This works:
const { S3Client } = require("@aws-sdk/client-s3")
const { Upload } = require('@aws-sdk/lib-storage')
const stream = require('stream')
const s3Region = 'us-east-1'
const bucketname = "my_bucket"
exports.handler = function (event, context, callback) {
let streamfrom = stream.Readable.from(["four"])
getS3Stream()
.then(streamto => {
stream.pipeline(
streamfrom,
streamto,
() => {
callback(null, { 'statusCode': 200 })
})
})
}
function getS3Stream() {
return new Promise(resolve => {
const pass = new stream.PassThrough()
const upload = new Upload({
client: new S3Client({ region: s3Region }),
params: {
Bucket: bucketname,
Key: "test/test.txt",
Body: pass
}
})
upload.done().then((res, error) => {
if (error) { reject(error) }
console.log("s3 uploaded")
})
resolve(pass)
})
}
But I want the handler function to return a promise instead of using a callback, at which point it no longer works:
exports.handler = async function (event, context) {
return new Promise(resolve => {
let streamfrom = stream.Readable.from(["five5"])
getS3Stream()
.then(streamto => {
stream.pipeline(
streamfrom,
streamto,
() => {
resolve({ 'statusCode': 200 })
})
})
})
}
It returns {"statusCode":200}, but "s3 uploaded" is not printed, and the file does not appear in S3. Am I misunderstanding something about how to use promises here?
If you want your handler to return a Promise, you have to mark it as async.
I have not found any documentation on this but I believe the runtime caller first checks the handler definition so it know how it should call it.
Something like:
if (handler.constructor.name === 'AsyncFunction') {
result = await handler(event, context)
handleResult(null, result)
} else if (handler.constructor.name === 'Function') {
handler(event, context, handleResult)
} else {}
So, going back to your original code, it can be simply
exports.handler = async function (event, context) {
return new Promise(resolve => {
let streamfrom = stream.Readable.from(["five5"])
getS3Stream()
.then(streamto => {
stream.pipeline(
streamfrom,
streamto,
() => resolve({'statusCode': 200})
)
})
})
}
function getS3Stream() {
return new Promise(resolve => {
const pass = new stream.PassThrough()
const upload = new Upload({
client: new S3Client({region: s3Region}),
params: {
Bucket: bucketname,
Key: "test/test.txt",
Body: pass
}
})
upload.done().then((res, error) => {
if (error) {
reject(error)
}
console.log("s3 uploaded")
resolve(pass)
})
})
}
Inspired by this I came up with a solution that almost meets my qualifications. The function doesn't return just the stream, it also returns the promise. I am still hoping someone will come up with something that works without that crutch.
const { S3Client } = require("@aws-sdk/client-s3")
const { Upload } = require('@aws-sdk/lib-storage')
const stream = require('stream');
const s3Region = 'us-east-1'
const bucketname = "my_bucket"
exports.handler = async function (event, context) {
return new Promise(resolve => {
let streamfrom = stream.Readable.from(["five5"])
getS3Stream()
.then(({streamto, promise})=>{
stream.pipeline(
streamfrom,
streamto,
() => promise.then(()=>resolve({ 'statusCode': 200 }))
)
})
})
}
async function getS3Stream() {
const streamto = new stream.PassThrough()
const s3Client = new S3Client({ region: s3Region })
const upload = new Upload({
client: s3Client,
params: {
Bucket: bucketname,
Key: "test/test.txt",
Body: streamto
}
})
let promise = upload.done()
return({streamto,promise})
}