src/CallbackContext.js (92 lines of code) (raw):
/**
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*/
'use strict';
const BeforeExitListener = require('./BeforeExitListener.js');
const { structuredConsole } = require('./LogPatch');
/**
* Build the callback function and the part of the context which exposes
* the succeed/fail/done callbacks.
* @param client {RuntimeClient}
* The RAPID client used to post results/errors.
* @param id {string}
* The invokeId for the current invocation.
* @param scheduleNext {function}
* A function which takes no params and immediately schedules the next
* iteration of the invoke loop.
*/
function _rawCallbackContext(client, id, scheduleNext) {
const postError = (err, callback) => {
structuredConsole.logError('Invoke Error', err);
client.postInvocationError(err, id, callback);
};
let isCompleteInvoked = false;
const complete = (result, callback) => {
if (isCompleteInvoked) {
console.error(
'Invocation has already been reported as done. Cannot call complete more than once per invocation.',
);
return;
}
isCompleteInvoked = true;
client.postInvocationResponse(result, id, callback);
};
let waitForEmptyEventLoop = true;
const callback = function (err, result) {
BeforeExitListener.reset();
if (err !== undefined && err !== null) {
postError(err, scheduleNext);
} else {
if (!waitForEmptyEventLoop) {
complete(result, scheduleNext);
} else {
BeforeExitListener.set(() => {
setImmediate(() => {
complete(result, scheduleNext);
});
});
}
}
};
const done = (err, result) => {
BeforeExitListener.reset();
if (err !== undefined && err !== null) {
postError(err, scheduleNext);
} else {
complete(result, scheduleNext);
}
};
const succeed = (result) => {
done(null, result);
};
const fail = (err) => {
if (err === undefined || err === null) {
done('handled');
} else {
done(err, null);
}
};
const callbackContext = {
get callbackWaitsForEmptyEventLoop() {
return waitForEmptyEventLoop;
},
set callbackWaitsForEmptyEventLoop(value) {
waitForEmptyEventLoop = value;
},
succeed: succeed,
fail: fail,
done: done,
};
return [
callback,
callbackContext,
function () {
isCompleteInvoked = true;
},
];
}
/**
* Wraps the callback and context so that only the first call to any callback
* succeeds.
* @param callback {function}
* the node-style callback function that was previously generated but not
* yet wrapped.
* @param callbackContext {object}
* The previously generated callbackContext object that contains
* getter/setters for the contextWaitsForEmptyeventLoop flag and the
* succeed/fail/done functions.
* @return [callback, context]
*/
function _wrappedCallbackContext(callback, callbackContext, markCompleted) {
let finished = false;
const onlyAllowFirstCall = function (toWrap) {
return function () {
if (!finished) {
toWrap.apply(null, arguments);
finished = true;
}
};
};
callbackContext.succeed = onlyAllowFirstCall(callbackContext.succeed);
callbackContext.fail = onlyAllowFirstCall(callbackContext.fail);
callbackContext.done = onlyAllowFirstCall(callbackContext.done);
return [onlyAllowFirstCall(callback), callbackContext, markCompleted];
}
/**
* Construct the base-context object which includes the required flags and
* callback methods for the Node programming model.
* @param client {RAPIDClient}
* The RAPID client used to post results/errors.
* @param id {string}
* The invokeId for the current invocation.
* @param scheduleNext {function}
* A function which takes no params and immediately schedules the next
* iteration of the invoke loop.
* @return [callback, context]
* The same function and context object, but wrapped such that only the
* first call to any function will be successful. All subsequent calls are
* a no-op.
*/
module.exports.build = function (client, id, scheduleNext) {
let rawCallbackContext = _rawCallbackContext(client, id, scheduleNext);
return _wrappedCallbackContext(...rawCallbackContext);
};