Problem: I have very little experience working with async await functions and I am trying to execute a nested async await function within an if-else statement that depends on a higher level async function to execute upon an event detection. I expect to get a successful http response back from the nested async function, but I continue getting a null value for a response. The nested async function works as expected outside of the if-else statement, however. My goal is to simply be able to get the “await new Promise” part of the code to return a http response within the conditional if-else statement. Any help with this is appreciated.
What I’ve tried: I haven’t really made any attempts to remedy the situation besides searching for questions with similar issues since I know very little about the nature of async await functions.
Code:
exports.handler = async (event) => { const sensorId = event.sensorId; ddb.scan(params, function (err, data) { if (err) { console.log("Error", err); } else { console.log("Success", data); console.log(typeof(data)); data.Items.forEach(function (item, index, array) { console.log("printing", item); console.log('Testing', item.sensorId.S); if (item.sensorId.S == sensorId) { console.log('New sensorId was not created. Already Exists.'); return ; } else { // Here is the nested async await function async () => { console.log(event.sensorId); const req = new AWS.HttpRequest(appsyncUrl, region); const item = { input: { id: event.sensorId, sensorId: event.sensorId } }; req.method = "POST"; req.path = "/graphql"; req.headers.host = endpoint; req.headers["Content-Type"] = "application/json"; req.body = JSON.stringify({ query: print(createSensor), operationName: "createSensor", variables: item }); console.log(typeof(graphqlQuery)); if (apiKey) { req.headers["x-api-key"] = apiKey; } else { const signer = new AWS.Signers.V4(req, "appsync", true); signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate()); } const data = await new Promise((resolve, reject) => { const httpRequest = https.request({ ...req, host: endpoint }, (result) => { let data = ""; result.on("data", (chunk) => { data += chunk; }); result.on("end", () => { resolve(JSON.parse(data.toString())); }); }); httpRequest.write(req.body); httpRequest.end(); }); try { return { statusCode: 200, body: data }; } catch(err) { console.log('error', err); } }; }}); } });
Expected result:
Response { "statusCode": 200, "body": { "data": { "createSensor": { "id": "fd78597a-12fd-4bd1-9f9d-6ee1a88e197d", "digit": null, "date": null, "timestamp": null } } } }
Actual result:
Response null
Advertisement
Answer
There are a few issues with your code:
- The nested async function — you’re creating it but never executing it
async () => { // creates an async anonymous arrow function, that's it }
Two solutions:
// wrapping the function in an IIFE create and executes it (async () => { })(); // give it a name and execute it later async main() => { } main();
- You can get rid of the nested async function by declaring the callback passed to forEach as async:
data.Items.forEach(async (item, index, array) => { // <--- this is async // ... const req = new AWS.HttpRequest(appsyncUrl, region); const item = { input: { id: event.sensorId, sensorId: event.sensorId, }, }; // ... });
- The try/catch block at the end won’t catch any errors. Instead, wrap the Promise you created inside a try/catch block and reject from inside upon an error event:
try { const data = await new Promise((resolve, reject) => { const httpRequest = https.request( { ...req, host: endpoint }, (result) => { // ... result.on("error", (error) => { reject(error); }); // ... } ); // ... }); return { statusCode: 200, body: data, }; } catch (err) { console.log("error", err); }
- Running async operations inside forEach does not do what you intend to do. You probably intend to respond after all sensorsIds have been created. What really happens is that you respond as soon as the first sensorId is created. That’s because forEach fires the callbacks for
data.Items
simultaneously. A solution for this is to use map instead and return an array of Promises which you can then await with Promise.all.
Here’s the final code and how I would solve it. As an extra I’ve promisified ddb.scan
so you’re not mixing callbacks with promises and async/await:
const scanAsync = util.promisify(ddb.scan); exports.handler = async (event) => { const sensorId = event.sensorId; try { const data = await scanAsync(params); const responses = await Promise.all( data.Items.map((item) => { if (item.sensorId.S == sensorId) { console.log("New sensorId was not created. Already Exists."); return; } const req = new AWS.HttpRequest(appsyncUrl, region); const item = { input: { id: event.sensorId, sensorId: event.sensorId, }, }; req.method = "POST"; req.path = "/graphql"; req.headers.host = endpoint; req.headers["Content-Type"] = "application/json"; req.body = JSON.stringify({ query: print(createSensor), operationName: "createSensor", variables: item, }); if (apiKey) { req.headers["x-api-key"] = apiKey; } else { const signer = new AWS.Signers.V4(req, "appsync", true); signer.addAuthorization( AWS.config.credentials, AWS.util.date.getDate() ); } return new Promise((resolve, reject) => { const httpRequest = https.request( { ...req, host: endpoint }, (result) => { let data = ""; result.on("data", (chunk) => { data += chunk; }); result.on("error", (error) => { reject(error); }); result.on("end", () => { resolve(JSON.parse(data.toString())); }); } ); httpRequest.write(req.body); httpRequest.end(); }); }) ); return { statusCode: 200, body: responses, }; } catch (error) { console.log("error", error); } };
I hope you learned a thing or two from my response :). Let me know if you have any questions.