We have an EnableRule (JS) for a ribbon button. Because it calls an api, it takes a bit and hence, should be async. The following implementation ilustrates the problem:
export async function ribbonEnabled():Promise<boolean> {
setInterval(() => {console.log(new Date().toLocaleTimeString())}, 1000);
await new Promise(r => setTimeout(r, 7000));
console.log('sleep is over');
return true;
}
Even tough the function is declared as async and awaits the api call (or in the example the timeout), the ribbon is not rendered until a value is returned.
Here you can see that several seconds have passed and no ribbon is rendered:
And as soon as the await procedure is over, the message gets printed and the ribbon is rendered:
Is that a known issue? Are there any workarounds? I have seen solution where the function is synchronous, returns a boolean variable and in a promis changes this variable and calls ui.refreshRibbon. But this is not adviced and runs the whole function again, so more logic is needed.
You can return promise object in your function. This is only support in Unified Interface.
function EnableRule()
{
const request = new XMLHttpRequest();
request.open('GET', '/bar/foo');
return new Promise(function (resolve, reject)
{
request.onload = function (e)
{
if (request.readyState === 4)
{
if (request.status === 200)
{
resolve(request.responseText === "true");
}
else
{
reject(request.statusText);
}
}
};
request.onerror = function (e)
{
reject(request.statusText);
};
request.send(null);
});
}
Notice If the promise does not resolve within 10 seconds, the rule will resolve with a false value.
You can get more detail here
Related
I am using NodeJS env with serverless framework.
The service is an endpoint for a contact form submission. Code looks something like this.
I have two async calls, one is writing to dynamoDB and another is sending an Email via SES.
module.exports.blog = async (event, context, callback) => {
const data = JSON.parse(event.body);
const handler = 'AB';
const sesParams = getSesParams(handler, data);
if (typeof data.text !== 'string') {
callback(null, validationErrRes);
return;
}
try {
await logToDB(handler, data);
} catch (dbErr) {
console.error(dbErr);
callback(null, errRes(dbErr, 'Failed to log to DB'));
return;
}
try {
await SES.sendEmail(sesParams).promise();
} catch (emailErr) {
console.error(emailErr);
callback(null, errRes(emailErr, 'Failed to send mail'));
return;
}
callback(null, succsessResponse);
return;
};
The response takes exactly 6sec when the dbput and sendMail takes total of < 300ms.
PS: Running both async calls parallelly does not help much.
Try removing the callback in your function definition and the call to your callback function. Just return the successResponse. You are already an async function so do not need to use a callback. You can also just return error.
module.exports.blog = async (event, context) => {
and
return {
statusCode: 200
}
and
return validationErrRes
I have a DynamoDB Put request wrapped into an async function.
async function putter(param1, param2) {
const paramsPut = {
TableName: MyTableName,
Item: {
"hashKey": param1,
"sortKey": param2,
}
};
dynamodb.put(paramsPut, function(err, data) {
if (err) {
console.log("Failure")
console.log(data)
return data
}
else {
console.log("Success")
console.log(data)
return data
}
});
};
The return for the async funtion is placed in the response function - this the should provide back a promise upon put operation was performed (either sucessfully or not sucessfully).
I then invoke this async put function from another async function:
var param1 = "50";
var param2 = "60";
async function main() {
await putter(param1 , param2)
console.log("Feedback received")
}
When I invoke this aysnc main function I would expect it to provide the Success statement from the put function prior to writing "Feedback received" as it should await the put function response.
However my console logs the "Feedback received" prior to
logging the "Success" statement in the put async function which I
was awaiting.
What am I missing here? Thanks for your support!
Try to change your code like follows:
try {
const data = await dynamodb.put(paramsPut).promise()
console.log("Success")
console.log(data)
return data
} catch (err) {
console.log("Failure", err.message)
// there is no data here, you can return undefined or similar
}
Almost every function from AWS SDK has the promise() variant to return the result as a Promise. Then you can just await the Promise. Don't mix callbacks with promises (async/await) - it makes the code hard to read, it's better to stick with one technique everywhere.
I've got 3 functions.
Cron job lambda function
Event driven function which detects when a new record is added to the DynamoDB
A reusable function which is currently called by the 2 above functions
The Cron job function
export async function scheduledFunction() {
const detailsHistory = await sharedFunction(param1);
}
The event driven function
export async function eventFunction(event) {
event.Records.forEach(async record => {
if (record.eventName === 'INSERT') {
await sharedFunction(param1)
}
}
}
The function called by both of the event and scheduled function
const sharedFunction = async (param1) {
const apiUrl = 'xxxxxx';
const details = await axios.get(apiUrl, {
headers: {
'x-api-key': xxxx
}
});
}
The event function works when the DynamoDB has a new insert and then calls the 3rd party API which works as expected
The scheduled function fires every 4 hours and is works and gets to the sharedFunction, but when its gets to the API call await axios.get it just does nothing, I'm not getting any errors in the CloudWatch. I've placed console.logs() before and after the call and it logs the one before but nothing after.
You should always put async code inside try ... catch block. Also forEach won't work with promise you will need to use for loop. Try this:
export async function eventFunction(event) {
try {
for (let record of event.Records) {
if (record.eventName === 'INSERT') {
await sharedFunction(param1)
}
}
}
catch (err) {
console.log(err);
return err;
}
}
Shared function:
const sharedFunction = async (param1) => {
try {
const apiUrl = 'xxxxxx';
return await axios.get(apiUrl, {
headers: {
'x-api-key': xxxx
}
});
}
catch (err) {
return err;
}
}
Scenario
I'm trying to do multiple it specs on a single external load rather than have the external data loaded EVERY time.
Question
How can I do this with a single call of getExternalValue while still keeping my it definitions?
Ideas
Currently I'm doing all the expects in a single it block. I've also thought about storing the loaded value before my tests but then I'd have to find another way to make jasmine wait until the value is loaded.
Code
function getExternalValue(callback) {
console.log("getting external value");
setTimeout(function() {
callback(true);
}, 2000);
return false;
}
describe("mjaTestLambda()", function() {
it("is truthy", function(done) {
let truthy;
truthy = getExternalValue(function(bool) {
truthy = bool;
expect(truthy).toBeTruthy();
done();
});
});
it("is falsy", function(done) {
let truthy;
truthy = getExternalValue(function(bool) {
truthy = bool;
expect(!truthy).toBeFalsy();
done();
});
});
});
How can I do this with a single call of getExternalValue while still
keeping my it definitions?
Use beforeEach() or beforeAll() to get the resolved value. Personally I suggest beforeEach() as it will reset the value for each test and helps ensure a clean setup for each one.
I noticed your function has a callback parameter. Async/await is a useful pattern that works best when (1) you're writing async/await functions or (2) your functions return a Promise. If you need to keep the callback parameter, let me know and I'll update the following:
// returns Promise
function getExternalValue() {
return new Promise((resolve, reject) => {
console.log("getting external value");
setTimeout(() => {
resolve(true);
}, 2000);
});
}
describe("mjaTestLambda()", () => {
let value;
beforeAll(() => {
return getExternalValue()
.then((v) => { value = v; });
});
it("is truthy", () => {
expect(v).toBeTruthy();
});
it("is not falsy", () => {
expect(!v).toBeFalsy();
});
});
I am new to Rxjs and am trying to implement the following workflow in it:
User clicks on a menu item that triggers an HTTP request
Before the response has arrived, the user clicks on a second request
The subscription to the first request is ended and a subscription to the second request is started
// The code below sits inside the onClick event of my menu
var callAction = function(someParameters) {
return Rx.Observable.create(function(observer) {
var subscribed = true;
myHttpApi.someActionCall(someParameters).then(
(data: any) => {
if (subscribed) {
// Send data to the client
observer.next(data);
// Immediately complete the sequence
observer.complete();
}
}).catch((err: any) => {
if (subscribed) {
// Inform the client that an error occurred.
observer.error(ex);
}
}
);
return function () {
subscribed = false;
}
});
};
The observer is further defined below:
var observer = {
// onNext in RxJS 4
next: function (data) {
// Do what you need to do in the interface
},
// onError in RxJS 4
error: function (err) {
// Handle the error in the interface
},
// onComplete in RxJS 4
complete: function () {
//console.log("The asynchronous operation has completed.");
}
};
let subscription = callAction(somParameters).subscribe(observer);
How do I now go about implementing #3, whereby the subscription to the first request is ended and a subscription to the new request (in this example, the same block of code is executed for different menu options and therefore different requests based on the parameters) is started?
Breaking up the steps into discrete functions,
// Inner observable, calls the API
const callAction$ = function(someParameters) {
return Observable.fromPromise(
myHttpApi.someActionCall(someParameters)
)
}
// Outer observable, controls the click chain
const click$ = new Subject();
click$.switchMap(clickParams => {
return callAction$(clickParams)
})
.subscribe(
result => console.log('Result: ', result),
err => console.log('Error: ', err.message)
)
// Handler function, called from menu
const handleClick = function(clickParams) {
click$.next(clickParams)
}
Working example CodePen