Very first thing I've ever done in Node.js, I'm writing an AWS Lambda function, and I want to check whether a custom attribute on a User has a value before doing anything else. Since I'm told Promises are the way to handle asynchronous methods synchronously, I wrote the function:
var AWS = require('aws-sdk');
var s3 = new AWS.S3();
var cogId = new AWS.CognitoIdentityServiceProvider();
exports.handler = function (event, context) {
if (event != null)
{
var identityId = context.identity.cognitoIdentityId;
if (event.userId != null)
{
var userId = event.userId;
PromiseConfirmIdNotSet(userId)
.then(SetId(userId, identityId))
.catch();
}
}
context.done(null, 'Hello World'); // SUCCESS with message
};
function PromiseConfirmIdNotSet(userId)
{
console.log('Entering function');
return new Promise(function (resolve, reject) {
console.log('Entering Promise');
cogId.adminGetUser({
UserPoolId: myUserPool,
UserId: userId
},
function (err, data) {
console.log('err = ' + JSON.stringify(err));
console.log('data = ' + JSON.stringify(err));
if (data != null && data.UserAttributes.Name == null) {
console.log('Calling resolve');
resolve();
} else {
console.log('Calling reject');
reject();
}
});
});
console.log('Exiting Promise');
}
function SetId(userId, identityId)
{
cogId.updateUserAttributes();
}
But when I run it, the console log shows "Entering function", then "Entering Promise", then the execution goes to SetId
without ever having called the callback specified in adminGetUser
.
If I let the debugger continue after the main flow is done, eventually I do get the logs from the callback function, so it does eventually run.
Why is the Promise skipping to the then without the resolve
ever getting called?
.then
accepts a function as an argument. When you do
PromiseConfirmIdNotSet(userId)
.then(SetId(userId, identityId))
.catch();
PromiseConfirmIdNotSet
is called, and synchronously, SetId
is called, while the interpreter tries to construct a Promise
chain from the function passed to .then
. (But SetId
doesn't return a function) Then, after that, PromiseConfirmIdNotSet
's asynchronous code runs, and the Promise
resolves - which isn't in the order you want.
Change it so that SetId
is only called after the promise returned by PromiseConfirmIdNotSet
resolves:
PromiseConfirmIdNotSet(userId)
.then(() => SetId(userId, identityId))
.catch();
The problem is similar to why
addEventListener('click', fn());
doesn't work - you'd change it to , fn);
or , () => fn());
.
If you additionally want context.done
to occur only after a successful SetId
, then put the context.done
call inside the .then
:
PromiseConfirmIdNotSet(userId)
.then(() => {
SetId(userId, identityId);
context.done(null, 'Hello World'); // SUCCESS with message
});
You can simply use async-await for neat asynchronous functions. Here is your code with async await. Please check and let me know if you find any more issues.
exports.handler = async function (event, context) {
if (event != null)
{
var identityId = context.identity.cognitoIdentityId;
if (event.userId != null)
{
var userId = event.userId;
await PromiseConfirmIdNotSet(userId);
await SetId(userId, identityId);
}
}
await context.done(null, 'Hello World'); // SUCCESS with message
};
function PromiseConfirmIdNotSet(userId)
{
console.log('Entering function');
return new Promise(function (resolve, reject) {
console.log('Entering Promise');
cogId.adminGetUser({
UserPoolId: myUserPool,
UserId: userId
},
function (err, data) {
console.log('err = ' + JSON.stringify(err));
console.log('data = ' + JSON.stringify(err));
if (data != null && data.UserAttributes.Name == null) {
console.log('Calling resolve');
resolve();
} else {
console.log('Calling reject');
reject();
}
});
});
console.log('Exiting Promise');
}
function SetId(userId, identityId)
{
cogId.updateUserAttributes();
}