My app is trying to upload files to S3. S3 upload works fine. The problem is that after imageUpload returns, in handleSubmit(), it claims that the return value for imageUpload() is undefined. I suspect that it has to do with async/await, which I'm not too familiar with.
Can any expert explain what I'm missing?
async function imageUpload() {
const params = {
Bucket: BUCKET_NAME,
Key: product.media.name,
Body: product.media
};
s3.upload(params, function(s3Err, data) {
if (s3Err) throw s3Err
console.log(`File uploaded successfully at ${data.Location}`) // successfully get data.Location here
return data.Location
});
}
async function handleSubmit(event) {
try {
event.preventDefault();
setLoading(true)
setError('')
const mediaUrl = await imageUpload()
const url = `${baseUrl}/api/product`
const { name, desc } = product
const payload = { name, desc, mediaUrl } // mediaUrl is undefined here
const response = await axios.post(url, payload)
} catch(error) {
catchErrors(error, setError)
} finally {
setLoading(false)
}
}
You have to wrap your imageUpload code inside promise and then pass the data to resolve callback that you want to return, and if there is some error you pass them in reject callback, throwing error in asynchronous task can give unexpected behaviour, so use reject callback there.
async function imageUpload() {
const params = {
Bucket: BUCKET_NAME,
Key: product.media.name,
Body: product.media
};
return new Promise((resolve, reject) => {
s3.upload(params, function (s3Err, data) {
if (s3Err) {
reject(s3Error);
}
console.log(`File uploaded successfully at ${data.Location}`) // successfully get data.Location here
resolve(data.Location);
});
});
}
The problem is in your imageUpload function. You do not tell it to wait for response from s3.upload
function imageUpload() {
return new Promise((resolve, reject) => {
const params = {
Bucket: BUCKET_NAME,
Key: product.media.name,
Body: product.media
};
s3.upload(params, function(s3Err, data) {
if (s3Err) reject(s3Err)
else resolve(data.Location)
});
});
}
Your call to s3.upload is in an async function, but using a callback, and only returning to the callback (not to the outer function in any way). The AWS SDKs for JS all (or mostly all) support Promises now, so you should be able to do this:
async function imageUpload() {
const params = {
Bucket: BUCKET_NAME,
Key: product.media.name,
Body: product.media
};
const data = await s3.upload(params).promise()
console.log(`File uploaded successfully at ${data.Location}`)
return data
}
Related
I am using the package "fcm-node" in order to send notifications to certain device id.
the sendNotification function is as follows:
const FCM = require('fcm-node');
const serverKey = process.env.SERVER_KEY;
const fcm = new FCM(serverKey);
function sendNotification(registrationToken, title, body, type, key) {
const message = {
to: registrationToken,
collapse_key: key,
notification: {
title: title,
body: body,
delivery_receipt_requested: true,
sound: `ping.aiff`
},
data: {
type: type,
my_key: key,
}
};
fcm.send(message, function (err, value) {
if (err) {
console.log(err);
return false;
} else {
console.log(value);
return value;
}
});
};
module.exports = {
sendNotification
};
The api function I use to call this function is as follows:
router.get('/test', async (req, res, next) => {
const promise = new Promise((resolve, reject) => {
let data = sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
console.log(data)
if (data == false) reject(data);
else resolve(data);
});
promise
.then((data) => { return res.status(200).send(data); })
.catch((data) => { return res.status(500).send(data) })
});
When I console.log the "err" and "value" from the sendNotification, I get either of the followings:
{"multicast_id":4488027446433525506,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1652082785265643%557c6f39557c6f39"}]};
{"multicast_id":8241007545302148303,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
In case it is successful, I made sure that the device is receiving the notification.
The problem is in the api's data. It is always "undefined" and weither send notification is successful or not I get the 200 Ok status.
What seems to be the problem?
You can't return anything from the function (err, value) {} callback of a node-style asynchrnous function.
Your sendNotification() function needs to return a promise. util.promisify() makes the conversion from a node-style asynchronous function to a promise-returning asynchronous function convenient. Note the return, it's important:
const FCM = require('fcm-node');
const serverKey = process.env.SERVER_KEY;
const fcm = new FCM(serverKey);
const { promisify } = require('util');
fcm.sendAsync = promisify(fcm.send);
function sendNotification(registrationToken, title, body, type, key) {
return fcm.sendAsync({
to: registrationToken,
collapse_key: key,
notification: {
title: title,
body: body,
delivery_receipt_requested: true,
sound: `ping.aiff`
},
data: {
type: type,
my_key: key,
}
});
}
module.exports = {
sendNotification
};
Now you can do what you had in mind
router.get('/test', async (req, res, next) => {
try {
const data = await sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
return res.status(200).send(data);
} catch (err) {
return res.status(500).send(err);
}
});
Maybe it will help, at first try to return your response (the promise) in sendNotification, as actually you have a void function, that's why it's always undefined and after in your route
router.get('/test', async (req, res, next) => {
try {
const data = sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
if (data) {
return res.status(200).send(data);
}
} catch(err) {
return res.status(500).send(err);
}
});
I have some struggles with a conversion function I'm using. As my Mongodb documents saves before the conversion is complete which ends up with an array being empty, which should include my URLs(is verified in the callback). Everything is working fine, but my problem is that I redirect and save document way before apiInstance.convertDocumentPptxToPng is finished.
try {
const params = {
Bucket: 'bucket',
Key: req.file.key
}
await s3.getObject(params, async function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else {
const inputFile = await Buffer.from(data.Body)
apiInstance.convertDocumentPptxToPng(inputFile, callback)
}
await course.save()
res.redirect('/course/admin')
})} catch(err) {
console.log(err)
}
You are awaiting s3.getObject, but you are also giving it a callback function. Remove the callback function and just use await.
course is undefined, not sure it's normal though.
try {
const params = {
Bucket: 'bucket',
Key: req.file.key
}
const data = await new Promise( (resolve, reject) => s3.getObject(params,(err, data) => resolve(data)));
const inputFile = await Buffer.from(data.Body);
apiInstance.convertDocumentPptxToPng(inputFile, callback);
await course.save();
res.redirect('/course/admin');
} catch (err) {
console.log(err)
}
I have this code in TypeScript that I used to write a csv file to AWS S3, which it works fine locally, and recently I started getting and error saying:
s3 upload error unsupported body payload object
NOTES:
I'm not passing the credentials because the code is running in the
same container with AWS S3 (EC2) that's why I don't need to pass the
credentials.
I'm printing all the params I'm reading/passing and I have them
read properly.
Here is the code:
public async writeFileToS3(datasetFile: any): Promise<boolean> {
try {
const readFile = util.promisify(this.fileWriter.readFile);
const unlinkFile = util.promisify(this.fileWriter.unlink);
const s3BucketName = this.appConfig.get<string>(
'infra.fileWriter.bucket'
);
const s3Region = this.appConfig.get<string>(
'infra.fileWriter.region'
);
this.s3Bucket.config.region = s3Region;
console.log(
`datasetFile ${datasetFile.path} ${datasetFile.originalname}`
);
const data = readFile(datasetFile.path);
const params = {
Bucket: s3BucketName,
Key: datasetFile.originalname,
Body: data,
ACL: 'public-read'
};
console.log(
`params ${params.Bucket} ${params.Key} ${params.Body} ${params.ACL}`
);
return await new Promise<boolean>((resolve, reject) => {
this.s3Bucket.upload(params, function(err: any) {
unlinkFile(datasetFile.path);
if (err) {
console.log(err);
throw new OperationError(
'Error wirting file to S3',
err
);
} else {
resolve(true);
}
});
});
} catch (err) {
throw new OperationError('Error wirting file to S3');
}
}
readFile returns a Promise (you created it with util.promisify), thus data is a Promise here:
const data = readFile(datasetFile.path);
const params = {
Bucket: s3BucketName,
Key: datasetFile.originalname,
Body: data,
ACL: 'public-read'
};
You should await the Promise:
const data = await readFile(datasetFile.path);
I am trying to write a function that would:
Take a remote URL as a parameter,
Get the file using axios
Upload the stream to amazon s3
And finally, return the uploaded url
I found help here on stackoverflow. So far, I have this:
/*
* Method to pipe the stream
*/
const uploadFromStream = (file_name, content_type) => {
const pass = new stream.PassThrough();
const obj_key = generateObjKey(file_name);
const params = { Bucket: config.bucket, ACL: config.acl, Key: obj_key, ContentType: content_type, Body: pass };
s3.upload(params, function(err, data) {
if(!err){
return data.Location;
} else {
console.log(err, data);
}
});
return pass;
}
/*
* Method to upload remote file to s3
*/
const uploadRemoteFileToS3 = async (remoteAddr) => {
axios({
method: 'get',
url: remoteAddr,
responseType: 'stream'
}).then( (response) => {
if(response.status===200){
const file_name = remoteAddr.substring(remoteAddr.lastIndexOf('/')+1);
const content_type = response.headers['content-type'];
response.data.pipe(uploadFromStream(file_name, content_type));
}
});
}
But uploadRemoteFileToS3 does not return anything (because it's a asynchronous function). How can I get the uploaded url?
UPDATE
I have further improved upon the code and wrote a class. Here is what I have now:
const config = require('../config.json');
const stream = require('stream');
const axios = require('axios');
const AWS = require('aws-sdk');
class S3RemoteUploader {
constructor(remoteAddr){
this.remoteAddr = remoteAddr;
this.stream = stream;
this.axios = axios;
this.config = config;
this.AWS = AWS;
this.AWS.config.update({
accessKeyId: this.config.api_key,
secretAccessKey: this.config.api_secret
});
this.spacesEndpoint = new this.AWS.Endpoint(this.config.endpoint);
this.s3 = new this.AWS.S3({endpoint: this.spacesEndpoint});
this.file_name = this.remoteAddr.substring(this.remoteAddr.lastIndexOf('/')+1);
this.obj_key = this.config.subfolder+'/'+this.file_name;
this.content_type = 'application/octet-stream';
this.uploadStream();
}
uploadStream(){
const pass = new this.stream.PassThrough();
this.promise = this.s3.upload({
Bucket: this.config.bucket,
Key: this.obj_key,
ACL: this.config.acl,
Body: pass,
ContentType: this.content_type
}).promise();
return pass;
}
initiateAxiosCall() {
axios({
method: 'get',
url: this.remoteAddr,
responseType: 'stream'
}).then( (response) => {
if(response.status===200){
this.content_type = response.headers['content-type'];
response.data.pipe(this.uploadStream());
}
});
}
dispatch() {
this.initiateAxiosCall();
}
async finish(){
//console.log(this.promise); /* return Promise { Pending } */
return this.promise.then( (r) => {
console.log(r.Location);
return r.Location;
}).catch( (e)=>{
console.log(e);
});
}
run() {
this.dispatch();
this.finish();
}
}
But still have no clue how to catch the result when the promise is resolved. So far, I tried these:
testUpload = new S3RemoteUploader('https://avatars2.githubusercontent.com/u/41177');
testUpload.run();
//console.log(testUpload.promise); /* Returns Promise { Pending } */
testUpload.promise.then(r => console.log); // does nothing
But none of the above works. I have a feeling I am missing something very subtle. Any clue, anyone?
After an upload you can call the getsignedurl function in s3 sdk to get the url where you can also specify the expiry of the url as well. You need to pass the key for that function. Now travelling will update with example later.
To generate a simple pre-signed URL that allows any user to view the
contents of a private object in a bucket you own, you can use the
following call to getSignedUrl():
var s3 = new AWS.S3();
var params = {Bucket: 'myBucket', Key: 'myKey'};
s3.getSignedUrl('getObject', params, function (err, url) {
console.log("The URL is", url);
});
Official documentation link
http://docs.amazonaws.cn/en_us/AWSJavaScriptSDK/guide/node-examples.html
Code must be something like this
function uploadFileToS3AndGenerateUrl(cb) {
const pass = new stream.PassThrough();//I have generated streams from file. Using this since this is what you have used. Must be a valid one.
var params = {
Bucket: "your-bucket", // required
Key: key , // required
Body: pass,
ContentType: 'your content type',
};
s3.upload(params, function(s3Err, data) {
if (s3Err) {
cb(s3Err)
}
console.log(`File uploaded successfully at ${data.Location}`)
const params = {
Bucket: 'your-bucket',
Key: data.key,
Expires: 180
};
s3.getSignedUrl('getObject', params, (urlErr, urlData) => {
if (urlErr) {
console.log('There was an error getting your files: ' + urlErr);
cb(urlErr);
} else {
console.log(`url: ${urlData}`);
cb(null, urlData);
}
})
})
}
Please check i have update your code might its help you.
/*
* Method to upload remote file to s3
*/
const uploadRemoteFileToS3 = async (remoteAddr) => {
const response = await axios({
method: 'get',
url: remoteAddr,
responseType: 'stream'
})
if(response.status===200){
const file_name = remoteAddr.substring(remoteAddr.lastIndexOf('/')+1);
const content_type = response.headers['content-type'];
response.data.pipe(uploadFromStream(file_name, content_type));
}
return new Promise((resolve, reject) => {
response.data.on('end', (response) => {
console.log(response)
resolve(response)
})
response.data.on('error', () => {
console.log(response);
reject(response)
})
})
};
*
* Method to pipe the stream
*/
const uploadFromStream = (file_name, content_type) => {
return new Promise((resolve, reject) => {
const pass = new stream.PassThrough();
const obj_key = generateObjKey(file_name);
const params = { Bucket: config.bucket, ACL: config.acl, Key: obj_key, ContentType: content_type, Body: pass };
s3.upload(params, function(err, data) {
if(!err){
console.log(data)
return resolve(data.Location);
} else {
console.log(err)
return reject(err);
}
});
});
}
//call uploadRemoteFileToS3
uploadRemoteFileToS3(remoteAddr)
.then((finalResponse) => {
console.log(finalResponse)
})
.catch((err) => {
console.log(err);
});
I'm building a graphql server and one of the resolvers should return an url that is fetched from the aws api. I have tried in hours with promises, async await but nothing have worked yet.
What happens in the code i the following:
1) i make a call to aws api, and get a signed url in the callback.
2) i want to return that url in the graphql resolver function - getSignedURL
My question is: How can i make a resolver function return a result that i've got in another functions callback?
I will appreciate any help!
IN CLASS S3Store
var s3 = new aws.S3();
newSignedUrl(callback){
var params = {
Bucket: 'testBucket28993746',
Key: uuid.v4(),
Expires: 100,
ContentType: 'image/jpg'
};
s3.getSignedUrl('putObject', params, (err, signedURL)=>{
callback(err,signedURL);
});
}
Graphql resolver
getSignedURL(){//TODO: more secure, require auth first
let newURL = null;
s3store = new S3Store();
s3store.newSignedUrl((err,result)=>{
if(err){
console.log(err)
newURL = {}
} else{
console.log(result);
newURL = result;
}
});
return newURL;
},
When i make a call to the graphql endpoint, i get following:
{
"data": {
"getSignedURL": null
}
}
This woked for me:
IN CLASS S3Store
getSignedUrl(){
var params = {
Bucket: 'testBucket28993746',
Key: uuid.v4(),
Expires: 100,
ContentType: 'image/jpg'
};
return new Promise ((resolve, reject)=> { s3.getSignedUrl('putObject',params, (err, signedURL)=>{
if(err){
reject(err);
} else {
resolve( signedURL);
// console.log(signedURL);
console.log("in else ")
}
})
})
}
Graphql resolver
getSignedURL(){
return new S3Store().getSignedUrl().then((url)=> {return url});
}