Attempting to check if s3 bucket item exists within nested asyncs - amazon-s3

I have a Serverless Lambda function that, in response to an S3 s3:ObjectCreated event, tries to check if a separate item exists in an S3 bucket using the following bit of code using the AWS JavaScript SDK:
exports.somethingSomeSomething = async (event) => {
event.Records.forEach(async (record) => {
let tst = await s3.headObject({
Bucket: "mybucket",
Key: "something.gz"
}).promise()
console.log(tst)
})
};
I'm quite rusty with promises in JS, so I'm not sure why this bit of code doesn't work. For reference, it just dies without outputting anything.
However, the following does work:
exports.somethingSomething = async (event) => {
let tst = await s3.headObject({
Bucket: "mybucket",
Key: "something.gz"
}).promise()
console.log(tst)
console.log("RED")
};
How can I get the initial bit of code working, and what am I doing wrong?

It's because your code is async, but the function passed to your forEach loop is also async, so you have an async function invoking another chunk of async code, therefore you lose control of the flow. Whatever is inside forEach will run (although anything after forEach will run before whatever is inside forEach), but it will execute asynchronously and you are unable to keep track of its execution.
But if the code, as I said, will run, why don't you see the results?
Well, that's because Lambda will terminate before that code has the chance to execute. If you run the same piece of code locally, you'll see it will run just fine, but since the original code runs on top of Lambda, you don't have control when it terminates.
You have two options here:
The easiest is to grab the first item in the Records array because s3 events send one and only one event per invocation. The reason it is an array is because the way AWS works (a common interface for all events). Anyways, your forEach is not using anything of the Record object, but still if you wanted to use any properties of it, simply reference the 0th position, like so:
exports.somethingSomeSomething = async (event) => {
const record = event.Records[0]
//do something with record
const tst = await s3.headObject({
Bucket: "mybucket",
Key: "something.gz"
}).promise()
console.log(tst)
};
If you still want to use a for loop to iterate through the records (although, again, unnecessary for s3 events), use a for of loop instead:
exports.somethingSomeSomething = async (event) => {
for (const record of event.Records) {
// do something with record
const tst = await s3.headObject({
Bucket: "mybucket",
Key: "something.gz"
}).promise()
console.log(tst)
}
};
Since for of is just a regular loop, it will use the async from the function it's being executed on, so await is perfectly valid inside it.
More on async/await and for..of

Related

How can I use the same value as written in the Json during the same test execution in the testcafe

I have been trying to use the value from the JSON that I have got added successfully using fs.write() function,
There are two test cases in the same fixture, one to create an ID and 2nd to use that id. I can wrote the id successfully in the json file using fs.write() function and trying to use that id using importing json file like var myid=require('../../resources/id.json')
The json file storing correct id of the current execution but I get the id of first test execution in 2nd execution.
For example, id:1234 is stored during first test execution and id:4567 is stored in 2nd test execution. During 2nd test execution I need the id:4567 but I get 1234 this is weird, isn't it?
I use it like
t.typeText(ele, myid.orid)
my json file contains only id like {"orid":"4567"}
I am new to Javascript and Testcafe any help would really be appreciated
Write File class
const fs = require('fs')
const baseClass =require('../component/base')
class WriteIntoFile{
constructor(orderID){
const OID = {
orderid: orderID
}
const jsonString = JSON.stringify(OID)
fs.writeFile(`resources\id.json`, jsonString, err => {
if (err) {
console.log('Error writing file', err)
} else {
console.log('Successfully wrote file')
}
})
}
}
export default WriteIntoFile
I created 2 different classes in order to separate create & update operations and call the functions of create & update order in single fixture in test file
Create Order class
class CreateOrder{
----
----
----
async createNewOrder(){
//get text of created ordder and saved order id in to the json file
-----
-----
-----
const orId= await baseclass.getOrderId();
new WriteIntoFile(orId)
console.log(orId)
-----
-----
-----
}
}export default CreateOrder
Update Order class
var id=require('../../resources/id.json')
class UpdateOrder{
async searchOrderToUpdate(){
await t
***//Here, I get old order id that was saved during previous execution***
.typeText(baseClass.searchBox, id.orderid)
.wait(2500)
.click(baseClass.searchIcon)
.doubleClick(baseClass.orderAGgrid)
console.log(id.ordderid)
----
----
async updateOrder(){
this.searchOrderToUpdate()
.typeText(baseClass.phNo, '1234567890')
.click(baseClass.saveBtn)
}
}export default UpdateOrder
Test file
const newOrder = new CreateOrder();
const update = new UpdateOrder();
const role = Role(`siteurl`, async t => {
await t
login('id')
await t
.wait(1500)
},{preserveUrl:true})
test('Should be able to create an Order', async t=>{
await newOrder.createNewOrder();
});
test('Should be able to update an order', async t=>{
await update.updateOrder();
});
I'll reply to this, but you probably won't be happy with my answer, because I wouldn't go down this same path as you proposed in your code.
I can see a couple of problems. Some of them might not be problems right now, but in a month, you could struggle with this.
1/ You are creating separate test cases that are dependent on each other.
This is a problem because of these reasons:
what if Should be able to create an Order doesn't run? or what if it fails? then Should be able to update an order fails as well, and this information is useless, because it wasn't the update operation that failed, but the fact that you didn't meet all preconditions for the test case
how do you make sure Should be able to create an Order always runs before hould be able to update an order? There's no way! You can do it like this when one comes before the other and I think it will work, but in some time you decide to move one test somewhere else and you are in trouble and you'll spend hours debugging it. You have prepared a trap for yourself. I wrote this answer on this very topic, you can read it.
you can't run the tests in parallel
when I read your test file, there's no visible hint that the tests are dependent on each other. Therefore as a stranger to your code, I could easily mess things up because I have no way of knowing about it without going deeper in the code. This is a big trap for anyone who might come to your code after you. Don't do this to your colleagues.
2/ Working with files when all you need to do is pass a value around is too cumbersome.
I really don't see a reason why you need to same the id into a file. A slightly better approach (still violating 1/) could be:
const newOrder = new CreateOrder();
const update = new UpdateOrder();
// use a variable to pass the orderId around
// it's also visible that the tests are dependent on each other
let orderId = undefined;
const role = Role(`siteurl`, async t => {
// some steps, I omit this for better readability
}, {preserveUrl: true})
test('Should be able to create an Order', async t=>{
orderId = await newOrder.createNewOrder();
});
test('Should be able to update an order', async t=>{
await update.updateOrder(orderId);
});
Doing it like this also slightly remedies what I wrote in 1/, that is that it's not visible at first sight that the tests are dependent on each other. Now, this is a bit improved.
Some other approaches how you can pass data around are mentioned here and here.
Perhaps even a better approach is to use t.fixtureCtx object:
const newOrder = new CreateOrder();
const update = new UpdateOrder();
const role = Role(`siteurl`, async t => {
// some steps, I omit this for better readability
}, {preserveUrl:true})
test('Should be able to create an Order', async t=>{
t.fixtureCtx.orderId = await newOrder.createNewOrder();
});
test('Should be able to update an order', async t=>{
await update.updateOrder(t.fixtureCtx.orderId);
});
Again, I can at least see the tests are dependent on each other. That's already a big victory.
Now back to your question:
During 2nd test execution I need the id:4567 but I get 1234 this is weird, isn't it?
No, it's not weird. You required the file:
var id = require('../../resources/id.json')
and so it's loaded once and if you write into the file later, you won't read the new content unless you read the file again. require() is a function in Node to load modules, and it makes sense to load them once.
This demonstrates the problem:
const idFile = require('./id.json');
const fs = require('fs');
console.log(idFile); // { id: 5 }
const newId = {
'id': 7
};
fs.writeFileSync('id.json', JSON.stringify(newId));
// it's been loaded once, you won't get any other value here
console.log(idFile); // { id: 5 }
What you can do to solve the problem?
You can use fs.readFileSync():
const idFile = require('./id.json');
const fs = require('fs');
console.log(idFile); // { id: 5 }
const newId = {
'id': 7
};
fs.writeFileSync('id.json', JSON.stringify(newId));
// you need to read the file again and parse its content
const newContent = JSON.parse(fs.readFileSync('id.json'));
console.log(newContent); // { id: 7 }
And this is what I warned you against in the comment section. That this is too cumbersome, inefficient, because you write to a file and then read from the file just to get one value.
What you created is not very readable either:
const fs = require('fs')
const baseClass =require('../component/base')
class WriteIntoFile{
constructor(orderID){
const OID = {
orderid: orderID
}
const jsonString = JSON.stringify(OID)
fs.writeFile(`resources\id.json`, jsonString, err => {
if (err) {
console.log('Error writing file', err)
} else {
console.log('Successfully wrote file')
}
})
}
}
export default WriteIntoFile
All these operations for writing into a file are in a constructor, but a constructor is not the best place for all this. Ideally you have only variable assignments in it. I also don't see much reason for why you need to create a new class when you are doing only two operations that can easily fit on one line of code:
fs.writeFileSync('orderId.json', JSON.stringify({ orderid: orderId }));
Keep it as simple as possible. it's more readable like so than having to go to a separate file with the class and decypher what it does there.

recursive lambda function never seems to run

I'm not familiar enough with node.js or lambda to see an obvious solution to a dilemma I have. I'm writing some utilities on lambda to manipulate images in an S3 bucket and make them accessible via the GatewayAPI to rest calls.
BACKGROUND DETAILS:
One of the utilities I have retrieves the headObject information such as the mtime, size and metadata. The images themselves will likely be coming in from various means and I won't always have control over adding metadata to them when they arrive/are-created. But I don't really need it until it's necessary to view details about the image from a web interface. And when I do that, I use a thumbnail instead so I created a lambda create-event triggered script (and also have a fall back variation of it via the gatewayAPI) that will create a thumbnail (either when the image is first uploaded to S3 or whenever I make the gateway CreateThumbbnail call) at which time it adds metadata to the thumbnail for the image with things like the original image mimetype, pixel width and height.
What I would like to be able to do, is to create a 'GetObjectInfo' that firsts pulls the headObject data, then checks to see if the bucket specified is or is not the bucket with the associated thumbnail files. (e.g. if it is or is not a thumbnail object) If it is 'not' a thumbnail, I want to then go retrieve -- or at least attempt to retrieve -- the headObject for the associated thumbnail file and attach the thumbnail file's metadata (if the thumbnail exists) onto the data from the original head request before returning the information.
The problem is, that when I set up an async callback scheme, the first headObject request completes, the second never seems to get out of the starting gate.
The method in my class is:
getHeadObject(bucket,object,callback) {
console.log(bucket, "CLASS-head#1")
this.s3.headObject({"Bucket":bucket,"Key":object}, function(err,data){
console.log(bucket, "CLASS-head#2")
callback(err,data)
})
}
getObjectInfo(bucket,object,callback) {
let scope = this
console.log(bucket,"CLASS-object#1")
this.getHeadObject(bucket,object,function(err,data) {
console.log(bucket,"CLASS-object#2")
if(err)
callback(err,data)
else
callback(null,data)
})
}
The lambda code that calls it recursively is:
var cInst = new myClass()
cInst.getObjectInfo(srcBucket,filePath,function(err,data) {
if(data.status == 1) { // if parent request success
// if parent is not thumbnail
if(srcBucket != THUMB_BUCKET) { // see if a thumbnail exists
let thumbPath = myClass.getThumbPath(srcBucket,userId,directory,targetObject)
console.log('---- thumbPath', thumbPath)
cInst.getObjectInfo(THUMB_BUCKET,thumbPath, function(err,thumbData) {
console.log("thumbData #1",thumbData)
if(thumbData.status == 1) { // thumbnail exists
console.log("thumbData")
}
})
}
context.succeed(myClass.createResponse(1, data, api))
} else {
context.fail(myClass.createResponse(data.status, data, api))
}
})
First call on the parent is see
{bucket} "CLASS-object#1"
{bucket} "CLASS-head#1"
{bucket} "CLASS-head#2"
{bucket} "CLASS-object#2"
on the second I only see:
image-thumbnails "CLASS-object#1"
image-thumbnails "CLASS-head#1"
(getThumbPath is just a static utility function that builds the thumbnail path based on the parameters related to the original file. It is already tested as working and produces something like {original-bucket-name}/{userid}/{subdirectory}/{file-basename_150x150.jpg} for any given image - I confirmed that in this instance, the thumbnail exists and matches the path returned by getThumbPath and the acl appears to have permission to read the bucket and the object)
UPDATE: More weirdness
I tried setting the permissions to publicly readable on the thumbnail and it worked. So I started messing with the acl. For the time being since I am still testing, I just gave the role for the scripts full S3 permissions.
But I noticed now that it's working and not working intermittently. One time it completes, the next time it doesn't. WTF is going on here?
I would bet that this is the most common problem that people see when using Node.js with Lambda.
When a Node.js Lambda reaches the end of the main thread, it ends all other threads. When it reaches the end of the handler, it stops all concurrent promises or async calls that are running.
To make sure that the lambda does not prematurely terminate those threads, wait until those promises are complete by using await.
In your case, the following will work: wrap any async calls in a promise and then await them.
await new Promise(async (resolve, reject) => {
cInst.getObjectInfo(srcBucket,filePath,function(err,data) {
if(data.status == 1) {
if(srcBucket != THUMB_BUCKET) {
...
...
await new Promise((resolve2, reject2) => {
cInst.getObjectInfo(THUMB_BUCKET,thumbPath, function(err,thumbData) {
...
...
resolve2();
})
})
}
context.succeed(myClass.createResponse(1, data, api))
resolve();
} else {
context.fail(myClass.createResponse(data.status, data, api))
reject();
}
})
})

Using promises in Mongoose

I am new to the Promise method used to retrieve multiple database records at the same time and I want to rewrite my existing code to use promises
I have this piece of code in Express:
getController.getData = function(req,res, collection, pagerender) {
var id = req.params.id;
collection.find({}, function(err, docs){
if(err) res.json(err);
else res.render(pagerender, {data:docs, ADusername: req.session.user_id, id: req.params.id});
console.log(docs);
});
};
Now I want to use promises here, so I can do more queries to the database. Anyone know how I can get this done?
First, check if collection.find({}) returns a promise. If it does, then you can call your code like:
collection.find({}).
then(function(docs){
res.render(pagerender, {data:docs, ADusername: req.session.user_id, id: req.params.id});
})
.catch( function(err) {
res.json(err);
})
If you want more calls here, just create new DB call and add another .then block.
I suggest you read the documentation on promises, just to get a general feeling about them (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then). You will also see how you can handle both success and rejection in the same function if you want.

FETCH API return undefined

I want to use Fetch API but i don' t really understand it's mecanism.
I have an in my HTML and i want to assign the result of my fetch with this code :
const weather = "http://api.apixu.com/v1/current.json?key=cba287f271e44f88a60143926172803&q=Paris";
const array = [];
fetch(weather)
.then(blob => blob.json())
.then(data => {
array.push(data.current.humidity)
console.log(array[0])
}
);
document.querySelector('h1').innerHTML = array[0];
i have the result with the console.log but the returns "undefined". can you explain why ?
thanks a lot
This is because the call to the API is asynchronous, meaning that the code is not executed just line by line as you write it. The callback only runs as soon as the call to the API has finished, basically meaning that
data => {
array.push(data.current.humidity)
console.log(array[0])
}
runs after
document.querySelector('h1').innerHTML = array[0];
So when you try to set your h1, array is still empty. If you want to set it as soon the data is available, you have to do it within the callback function:
data => {
array.push(data.current.humidity)
document.querySelector('h1').innerHTML = array[0];
}
This might seem weird at first, but keep in mind that you're only registering an anonymous function but not running it yet. You just define the function that you want to trigger as soon as something happens, in this case: when your API call has finished.

using bluebird promises with express to make API calls

I'm trying to get different chunks of data from a trello API using bluebird promises library. In my express router I'm using middleware isLoggedIn, and getBoards, which body looks something like:
trello.get("/1/members/me/boards") // resolves with array of board objects
.each((board) => {
// do some async stuff like saving board to db or other api calls, based on retrieved board
.catch(err => console.error('ERR: fetching boards error - ${err.message}'))
})
The question is: I want to redirect (res.redirect('/')) only when all boards were retrieved and saved. How can I do that? Where should I place xres.redirect('/') expression?
I think you need something like:
var Promise = require('bluebird');
var promises = [];
trello.get("/1/members/me/boards") // resolves with array of board objects
.each((board) => {
//
promises.push( /*some promisified async call that return a promise, saving data in db or whatever asynchronous action. The important bit is that this operation must return a Promise. */ );
});
//So now we have an array of promises. The async calls are getting done, but it will take time, so we work with the promises:
Promise.all(promises).catch(console.log).then( function(results){
/*This will fire only when all the promises are fullfiled. results is an array with the result of every async call to trello. */
res.redirect('/'); //now we are safe to redirect, all data is saved
} );
EDIT:
Actually, you can avoid some boilerplate code using map instead of each:
trello.get("/1/members/me/boards") // resolves with array of board objects
.map((board) => {
return somePromisifiedSaveToDbFunction(board);
}).all(promises).catch(console.log).then( function(results){
res.redirect('/');
} );