fix: return statement is not necessary to execute .then and .catch execution (#16802)

This commit is contained in:
Apeksha Bhosale 2022-09-30 07:01:05 +05:30 committed by GitHub
parent cafb0d37b0
commit a0646bca77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 134 additions and 169 deletions

View File

@ -1,6 +1,6 @@
import { ObjectsRegistry } from "../../../../support/Objects/Registry";
let agHelper = ObjectsRegistry.AggregateHelper,
const agHelper = ObjectsRegistry.AggregateHelper,
ee = ObjectsRegistry.EntityExplorer,
jsEditor = ObjectsRegistry.JSEditor,
locator = ObjectsRegistry.CommonLocators,
@ -10,7 +10,7 @@ let agHelper = ObjectsRegistry.AggregateHelper,
describe("Validate basic Promises", () => {
it("1. Verify storeValue via .then via direct Promises", () => {
let date = new Date().toDateString();
const date = new Date().toDateString();
cy.fixture("promisesBtnDsl").then((val: any) => {
agHelper.AddDsl(val, locator._spanButton("Submit"));
});
@ -254,7 +254,7 @@ return InspiringQuotes.run().then((res) => { showAlert("Today's quote for " + us
it("9. Bug 10150: Verify Promise.all via JSObjects", () => {
deployMode.NavigateBacktoEditor();
let date = new Date().toDateString();
const date = new Date().toDateString();
cy.fixture("promisesBtnDsl").then((val: any) => {
agHelper.AddDsl(val, locator._spanButton("Submit"));
});
@ -337,24 +337,27 @@ showAlert("Wonderful! all apis executed", "success")).catch(() => showAlert("Ple
"{{resetWidget('Input1').then(() => showAlert(Input1.text))}}",
);
deployMode.DeployApp(locator._widgetInputSelector("inputwidgetv2"));
agHelper.TypeText(locator._widgetInputSelector("inputwidgetv2"), "Update value")
agHelper.TypeText(
locator._widgetInputSelector("inputwidgetv2"),
"Update value",
);
agHelper.ClickButton("Submit");
agHelper.ValidateToastMessage("Test");
});
//Skipping until this bug this is addressed!
it.skip("12. Bug 9782: Verify .then & .catch (show alert should trigger) via JS Objects without return keyword", () => {
it("12. Bug 9782: Verify .then & .catch (show alert should trigger) via JS Objects without return keyword", () => {
deployMode.NavigateBacktoEditor();
cy.fixture("promisesBtnDsl").then((val: any) => {
agHelper.AddDsl(val);
});
jsEditor.CreateJSObject(`const user = 'You';
InspiringQuotes.run().then((res) => { showAlert("Today's quote for " + user + " is " + JSON.stringify(res.quote.body), 'success') }).catch(() => showAlert("Unable to fetch quote for " + user, 'warning'))`);
ee.SelectEntityByName("Button1");
ee.SelectEntityByName("Button1", "Widgets");
cy.get("@jsObjName").then((jsObjName) => {
propPane.EnterJSContext("onClick", "{{" + jsObjName + ".myFun1()}}");
});
agHelper.ClickButton("Submit");
agHelper.Sleep(1000);
agHelper
.GetNAssertContains(
locator._toastMsg,

View File

@ -251,6 +251,7 @@ export function* evaluateActionBindings(
* worker. Worker will evaluate a block of code and ask the main thread to execute it. The result of this
* execution is returned to the worker where it can resolve/reject the current promise.
*/
export function* evaluateAndExecuteDynamicTrigger(
dynamicTrigger: string,
eventType: EventType,
@ -260,17 +261,23 @@ export function* evaluateAndExecuteDynamicTrigger(
) {
const unEvalTree: DataTree = yield select(getUnevaluatedDataTree);
log.debug({ execute: dynamicTrigger });
const { requestChannel, responseChannel } = yield call(
const { isFinishedChannel } = yield call(
worker.duplexRequest,
EVAL_WORKER_ACTIONS.EVAL_TRIGGER,
{ dataTree: unEvalTree, dynamicTrigger, callbackData, globalContext },
{
dataTree: unEvalTree,
dynamicTrigger,
callbackData,
globalContext,
},
);
let keepAlive = true;
while (keepAlive) {
const { requestData } = yield take(requestChannel);
const { requestData } = yield take(isFinishedChannel);
log.debug({ requestData, eventType, triggerMeta, dynamicTrigger });
if (requestData.finished) {
keepAlive = false;
@ -326,20 +333,38 @@ export function* evaluateAndExecuteDynamicTrigger(
);
}
// Return value of a promise is returned
isFinishedChannel.close();
return result;
}
yield call(evalErrorHandler, requestData.errors);
if (requestData.trigger) {
isFinishedChannel.close();
}
}
export function* executeDynamicTriggerRequest(
mainThreadRequestChannel: Channel<any>,
) {
while (true) {
const { mainThreadResponseChannel, requestData, requestId } = yield take(
mainThreadRequestChannel,
);
log.debug({ requestData });
if (requestData?.trigger) {
// if we have found a trigger, we need to execute it and respond back
log.debug({ trigger: requestData.trigger });
yield spawn(
executeTriggerRequestSaga,
requestId,
requestData,
eventType,
responseChannel,
triggerMeta,
requestData.eventType,
mainThreadResponseChannel,
requestData.triggerMeta,
);
}
if (requestData?.errors) {
yield call(evalErrorHandler, requestData.errors);
}
}
}
@ -357,9 +382,10 @@ interface ResponsePayload {
* resolve or reject it with the data the execution has provided
*/
function* executeTriggerRequestSaga(
requestId: string,
requestData: { trigger: ActionDescription; subRequestId: string },
eventType: EventType,
responseChannel: Channel<unknown>,
responseFromExecutionChannel: Channel<unknown>,
triggerMeta: TriggerMeta,
) {
const responsePayload: ResponsePayload = {
@ -386,8 +412,9 @@ function* executeTriggerRequestSaga(
responsePayload.data.reason = { message: error.message };
responsePayload.success = false;
}
responseChannel.put({
responseFromExecutionChannel.put({
method: EVAL_WORKER_ACTIONS.PROCESS_TRIGGER,
requestId: requestId,
...responsePayload,
});
}
@ -548,8 +575,11 @@ function getPostEvalActions(
function* evaluationChangeListenerSaga() {
// Explicitly shutdown old worker if present
yield call(worker.shutdown);
yield call(worker.start);
const { mainThreadRequestChannel } = yield call(worker.start);
yield call(worker.request, EVAL_WORKER_ACTIONS.SETUP);
yield spawn(executeDynamicTriggerRequest, mainThreadRequestChannel);
widgetTypeConfigMap = WidgetFactory.getWidgetTypeConfigMap();
const initAction: {
postEvalActions: Array<ReduxAction<unknown>>;

View File

@ -228,8 +228,7 @@ describe("GracefulWorkerService", () => {
requestData,
);
const handlers = await duplexRequest.toPromise();
expect(handlers).toHaveProperty("requestChannel");
expect(handlers).toHaveProperty("responseChannel");
expect(handlers).toHaveProperty("isFinishedChannel");
expect(MockWorker.instance.postMessage).toBeCalledWith({
method,
requestData,
@ -237,54 +236,6 @@ describe("GracefulWorkerService", () => {
});
});
test("duplex request channel handler", async () => {
const w = new GracefulWorkerService(MockWorker);
await runSaga({}, w.start);
const mockChannel = (name = "mock") => ({
name,
take: jest.fn(),
put: jest.fn(),
flush: jest.fn(),
close: jest.fn(),
});
const workerChannel = channel();
const mockRequestChannel = mockChannel("request");
const mockResponseChannel = mockChannel("response");
runSaga(
{},
// @ts-expect-error: type mismatch
w.duplexRequestHandler,
workerChannel,
mockRequestChannel,
mockResponseChannel,
);
let randomRequestCount = Math.floor(Math.random() * 10);
for (randomRequestCount; randomRequestCount > 0; randomRequestCount--) {
workerChannel.put({
responseData: {
test: randomRequestCount,
},
});
expect(mockRequestChannel.put).toBeCalledWith({
requestData: {
test: randomRequestCount,
},
});
}
workerChannel.put({
responseData: {
finished: true,
},
});
expect(mockResponseChannel.put).toBeCalledWith({ finished: true });
expect(mockRequestChannel.close).toBeCalled();
});
test("duplex response channel handler", async () => {
const w = new GracefulWorkerService(MockWorker);
await runSaga({}, w.start);
@ -294,42 +245,27 @@ describe("GracefulWorkerService", () => {
expect(MockWorker.instance).toBeDefined();
return;
}
const mockChannel = (name = "mock") => ({
name,
take: jest.fn(),
put: jest.fn(),
flush: jest.fn(),
close: jest.fn(),
});
const mockWorkerChannel = mockChannel("worker");
const responseChannel = channel();
const mainThreadResponseChannel = channel();
const workerRequestId = "testID";
runSaga(
{},
// @ts-expect-error: type mismatch
w.duplexResponseHandler,
workerRequestId,
mockWorkerChannel,
responseChannel,
mainThreadResponseChannel,
);
MockWorker.instance.postMessage = jest.fn();
let randomRequestCount = Math.floor(Math.random() * 10);
for (randomRequestCount; randomRequestCount > 0; randomRequestCount--) {
responseChannel.put({
mainThreadResponseChannel.put({
test: randomRequestCount,
requestId: workerRequestId,
});
expect(MockWorker.instance.postMessage).toBeCalledWith({
test: randomRequestCount,
requestId: workerRequestId,
});
}
responseChannel.put({
finished: true,
});
expect(mockWorkerChannel.close).toBeCalled();
});
});

View File

@ -3,6 +3,7 @@ import { channel, Channel, buffers } from "redux-saga";
import _ from "lodash";
import log from "loglevel";
import WebpackWorker from "worker-loader!";
// import { executeDynamicTriggerRequest } from "sagas/EvaluationsSaga";
/**
* Wrap a webworker to provide a synchronous request-response semantic.
*
@ -50,13 +51,15 @@ export class GracefulWorkerService {
private readonly _workerClass: typeof WebpackWorker;
public mainThreadRequestChannel: Channel<any>;
public mainThreadResponseChannel: Channel<any>;
constructor(workerClass: typeof WebpackWorker) {
this.shutdown = this.shutdown.bind(this);
this.start = this.start.bind(this);
this.request = this.request.bind(this);
this._broker = this._broker.bind(this);
this.duplexRequest = this.duplexRequest.bind(this);
this.duplexRequestHandler = this.duplexRequestHandler.bind(this);
this.duplexResponseHandler = this.duplexResponseHandler.bind(this);
// Do not buffer messages on this channel
@ -64,6 +67,8 @@ export class GracefulWorkerService {
this._isReady = false;
this._channels = new Map<string, Channel<any>>();
this._workerClass = workerClass;
this.mainThreadRequestChannel = channel();
this.mainThreadResponseChannel = channel();
}
/**
@ -77,6 +82,10 @@ export class GracefulWorkerService {
// Inform all pending requests that we're good to go!
this._isReady = true;
yield put(this._readyChan, true);
yield spawn(this.duplexResponseHandler, this.mainThreadResponseChannel);
return {
mainThreadRequestChannel: this.mainThreadRequestChannel,
};
}
/**
@ -96,6 +105,8 @@ export class GracefulWorkerService {
this._evaluationWorker.removeEventListener("message", this._broker);
this._evaluationWorker.terminate();
this._evaluationWorker = undefined;
this.mainThreadRequestChannel.close();
this.mainThreadResponseChannel.close();
}
/**
@ -182,29 +193,8 @@ export class GracefulWorkerService {
const workerRequestId = `${method}__${_.uniqueId()}`;
// The worker channel is the main channel
// where the web worker messages will get posted
const workerChannel = channel();
this._channels.set(workerRequestId, workerChannel);
// The main thread will listen to the
// request channel where it will get worker messages
const mainThreadRequestChannel = channel();
// The main thread will respond back on the
// response channel which will be relayed to the worker
const mainThreadResponseChannel = channel();
// We spawn both the main thread request and response handler
yield spawn(
this.duplexRequestHandler,
workerChannel,
mainThreadRequestChannel,
mainThreadResponseChannel,
);
yield spawn(
this.duplexResponseHandler,
workerRequestId,
workerChannel,
mainThreadResponseChannel,
);
const isFinishedChannel = channel();
this._channels.set(workerRequestId, isFinishedChannel);
// And post the first message to the worker
this._evaluationWorker.postMessage({
method,
@ -214,75 +204,27 @@ export class GracefulWorkerService {
// Returning these channels to the main thread so that they can listen and post on it
return {
responseChannel: mainThreadResponseChannel,
requestChannel: mainThreadRequestChannel,
isFinishedChannel: isFinishedChannel,
};
}
*duplexRequestHandler(
workerChannel: Channel<any>,
requestChannel: Channel<any>,
responseChannel: Channel<any>,
) {
*duplexResponseHandler(mainThreadResponseChannel: Channel<any>) {
if (!this._evaluationWorker) return;
try {
let keepAlive = true;
while (keepAlive) {
// Wait for a message from the worker
const workerResponse: {
responseData: {
finished: unknown;
};
} = yield take(workerChannel);
const { responseData } = workerResponse;
// post that message to the request channel so the main thread can read it
requestChannel.put({ requestData: responseData });
// If we get a finished flag, the worker is requesting to end the request
if (responseData.finished) {
keepAlive = false;
// Relay the finished flag to the response channel as well
responseChannel.put({
finished: true,
});
}
}
} catch (e) {
log.error(e);
} finally {
// Cleanup
requestChannel.close();
}
}
*duplexResponseHandler(
workerRequestId: string,
workerChannel: Channel<any>,
responseChannel: Channel<any>,
) {
if (!this._evaluationWorker) return;
try {
let keepAlive = true;
const keepAlive = true;
while (keepAlive) {
// Wait for the main thread to respond back after a request
const response: { finished: unknown } = yield take(responseChannel);
// If we get a finished flag, the worker is requesting to end the request
if (response.finished) {
keepAlive = false;
continue;
}
const response: { finished: unknown; requestId: string } = yield take(
mainThreadResponseChannel,
);
// send response to worker
this._evaluationWorker.postMessage({
...response,
requestId: workerRequestId,
requestId: response.requestId,
});
}
} catch (e) {
log.error(e);
} finally {
// clean up everything
responseChannel.close();
workerChannel.close();
this._channels.delete(workerRequestId);
}
}
@ -290,12 +232,28 @@ export class GracefulWorkerService {
if (!event || !event.data) {
return;
}
const { requestId, responseData, timeTaken } = event.data;
const { promisified, requestId, responseData, timeTaken } = event.data;
const ch = this._channels.get(requestId);
// Channel could have been deleted if the request gets cancelled before the WebWorker can respond.
// In that case, we want to drop the request.
if (ch) {
ch.put({ responseData, timeTaken });
if (promisified) {
if (responseData.finished) {
if (ch) {
ch.put({ requestData: responseData, timeTaken, requestId });
this._channels.delete(requestId);
}
} else {
this.mainThreadRequestChannel.put({
requestData: responseData,
timeTaken,
requestId,
mainThreadResponseChannel: this.mainThreadResponseChannel,
});
}
} else {
if (ch) {
ch.put({ responseData, timeTaken, requestId });
}
}
}
}

View File

@ -122,6 +122,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -142,6 +143,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -165,6 +167,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -183,6 +186,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -202,6 +206,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -227,6 +232,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -249,6 +255,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -270,6 +277,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -289,6 +297,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -317,6 +326,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -342,6 +352,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -363,6 +374,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),
@ -387,6 +399,7 @@ describe("Add functions", () => {
expect(workerEventMock).lastCalledWith({
type: "PROCESS_TRIGGER",
requestId: "EVAL_TRIGGER",
promisified: true,
responseData: {
errors: [],
subRequestId: expect.stringContaining("EVAL_TRIGGER_"),

View File

@ -39,6 +39,7 @@ describe("promise execution", () => {
expect(postMessageMock).toBeCalledWith({
requestId,
type: "PROCESS_TRIGGER",
promisified: true,
responseData: expect.objectContaining({
subRequestId: expect.stringContaining(`${requestId}_`),
trigger: {
@ -116,6 +117,7 @@ describe("promise execution", () => {
method: "PROCESS_TRIGGER",
requestId,
success: true,
promisified: true,
},
}),
);
@ -153,6 +155,7 @@ describe("promise execution", () => {
method: "PROCESS_TRIGGER",
requestId,
success: true,
promisified: true,
},
}),
);
@ -164,6 +167,7 @@ describe("promise execution", () => {
method: "PROCESS_TRIGGER",
requestId,
success: false,
promisified: true,
},
}),
);

View File

@ -43,6 +43,7 @@ export const promisifyAction = (
type: EVAL_WORKER_ACTIONS.PROCESS_TRIGGER,
responseData,
requestId: workerRequestIdCopy,
promisified: true,
});
const processResponse = function(event: MessageEvent) {
const { data, method, requestId, success } = event.data;
@ -99,6 +100,7 @@ export const completePromise = (requestId: string, result: EvalResult) => {
result,
},
requestId,
promisified: true,
});
};

View File

@ -195,6 +195,7 @@ describe("evaluateAsync", () => {
await evaluateAsync(js, {}, "TEST_REQUEST", {});
expect(self.postMessage).toBeCalledWith({
requestId: "TEST_REQUEST",
promisified: true,
responseData: {
finished: true,
result: { errors: [], logs: [], result: 123, triggers: [] },
@ -209,6 +210,7 @@ describe("evaluateAsync", () => {
await evaluateAsync(js, {}, "TEST_REQUEST_1", {});
expect(self.postMessage).toBeCalledWith({
requestId: "TEST_REQUEST_1",
promisified: true,
responseData: {
finished: true,
result: {

View File

@ -63,6 +63,21 @@ export const EvaluationScripts: Record<EvaluationScriptType, string> = {
`,
};
const topLevelWorkerAPIs = Object.keys(self).reduce((acc, key: string) => {
acc[key] = true;
return acc;
}, {} as any);
function resetWorkerGlobalScope() {
for (const key of Object.keys(self)) {
if (topLevelWorkerAPIs[key]) continue;
if (key === "evaluationVersion") continue;
if (extraLibraries.find((lib) => lib.accessor === key)) continue;
// @ts-expect-error: Types are not available
delete self[key];
}
}
export const getScriptType = (
evalArgumentsExist = false,
isTriggerBased = false,
@ -232,6 +247,7 @@ export default function evaluateSync(
skipLogsOperations = false,
): EvalResult {
return (function() {
resetWorkerGlobalScope();
const errors: EvaluationError[] = [];
let logs: LogObject[] = [];
let result;
@ -305,6 +321,7 @@ export async function evaluateAsync(
evalArguments?: Array<any>,
) {
return (async function() {
resetWorkerGlobalScope();
const errors: EvaluationError[] = [];
let result;
let logs;