Skip to content

Commit 5be3542

Browse files
authored
fix: fallback to setTimeout when queueMicrotask fails in SF (#2017)
1 parent 34fdba4 commit 5be3542

File tree

6 files changed

+72
-10
lines changed

6 files changed

+72
-10
lines changed

packages/common/src/editors/dateEditor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { parse } from '@formkit/tempo';
22
import { BindingEventService } from '@slickgrid-universal/binding';
3-
import { createDomElement, emptyElement, extend, setDeepValue } from '@slickgrid-universal/utils';
3+
import { createDomElement, emptyElement, extend, queueMicrotaskOrSetTimeout, setDeepValue } from '@slickgrid-universal/utils';
44
import { Calendar, type FormatDateString, type Options } from 'vanilla-calendar-pro';
55

66
import { Constants } from './../constants.js';
@@ -206,7 +206,7 @@ export class DateEditor implements Editor {
206206
}
207207
}) as EventListener);
208208

209-
queueMicrotask(() => {
209+
queueMicrotaskOrSetTimeout(() => {
210210
this.calendarInstance = new Calendar(this._inputElm, this._pickerMergedOptions);
211211
this.calendarInstance.init();
212212
if (!compositeEditorOptions) {
@@ -226,7 +226,7 @@ export class DateEditor implements Editor {
226226
}
227227

228228
destroy(): void {
229-
queueMicrotask(() => {
229+
queueMicrotaskOrSetTimeout(() => {
230230
this.hide();
231231
this.calendarInstance?.destroy();
232232
emptyElement(this._editorInputGroupElm);

packages/common/src/services/filter.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type BasePubSubService } from '@slickgrid-universal/event-pub-sub';
2-
import { extend, stripTags } from '@slickgrid-universal/utils';
2+
import { extend, queueMicrotaskOrSetTimeout, stripTags } from '@slickgrid-universal/utils';
33
import { dequal } from 'dequal/lite';
44

55
import { Constants } from '../constants.js';
@@ -858,7 +858,7 @@ export class FilterService {
858858
// and we did not have time to convert it to a flat dataset yet (for SlickGrid to use),
859859
// we would end up calling the pre-filter too early because these pre-filter works only a flat dataset
860860
// for that use case (like Example 6), we can queue a microtask to be executed at the end of current task
861-
queueMicrotask(() => this.refreshTreeDataFilters());
861+
queueMicrotaskOrSetTimeout(() => this.refreshTreeDataFilters());
862862
}
863863
}
864864

packages/common/src/services/pagination.service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { SharedService } from './shared.service.js';
1414
import { propertyObserver } from './observers.js';
1515
import type { Observable, RxJsFacade } from './rxjsFacade.js';
1616
import { type SlickDataView, SlickEventHandler, type SlickGrid } from '../core/index.js';
17+
import { queueMicrotaskOrSetTimeout } from '@slickgrid-universal/utils';
1718

1819
export class PaginationService {
1920
protected _eventHandler: SlickEventHandler;
@@ -143,7 +144,7 @@ export class PaginationService {
143144
};
144145
}
145146
});
146-
queueMicrotask(() => {
147+
queueMicrotaskOrSetTimeout(() => {
147148
if (this.dataView) {
148149
this.dataView.setRefreshHints({ isFilterUnchanged: true });
149150
this.dataView.setPagingOptions({ pageSize: this.paginationOptions.pageSize, pageNum: this._pageNumber - 1 }); // dataView page starts at 0 instead of 1

packages/utils/src/__tests__/utils.spec.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { beforeEach, describe, expect, it, test } from 'vitest';
1+
import { beforeEach, describe, expect, it, test, vi } from 'vitest';
22

33
import {
44
addToArrayWhenNotExists,
@@ -26,6 +26,7 @@ import {
2626
toSnakeCase,
2727
uniqueArray,
2828
uniqueObjectArray,
29+
queueMicrotaskOrSetTimeout,
2930
} from '../utils.js';
3031

3132
function removeExtraSpaces(text: string) {
@@ -661,6 +662,57 @@ describe('Service/Utilies', () => {
661662
});
662663
});
663664

665+
describe('queueMicrotaskOrSetTimeout() method', () => {
666+
it('use queueMicrotask() when available', () =>
667+
new Promise((done: any) => {
668+
const callback = vi.fn();
669+
670+
// Mock queueMicrotask
671+
const queueMicrotaskSpy = vi.spyOn(global, 'queueMicrotask');
672+
673+
// Call the function being tested
674+
queueMicrotaskOrSetTimeout(callback);
675+
676+
// Wait for the callback to be executed
677+
setTimeout(() => {
678+
expect(queueMicrotaskSpy).toHaveBeenCalledWith(callback);
679+
expect(callback).toHaveBeenCalledTimes(1);
680+
queueMicrotaskSpy.mockRestore();
681+
done();
682+
}, 10);
683+
}));
684+
685+
it('use setTimeout() fallback when queueMicrotask() is not available', () =>
686+
new Promise((done: any) => {
687+
const callback = vi.fn();
688+
689+
// Mock queueMicrotask
690+
const queueMicrotaskSpy = vi.spyOn(global, 'queueMicrotask');
691+
queueMicrotaskSpy.mockImplementation(() => {
692+
// Simulate queueMicrotask throwing an error
693+
throw new Error('queueMicrotask not available');
694+
});
695+
696+
// Mock setTimeout
697+
const setTimeoutSpy = vi.spyOn(global, 'setTimeout');
698+
699+
// Call the function being tested
700+
queueMicrotaskOrSetTimeout(callback);
701+
702+
// Assertions
703+
expect(queueMicrotaskSpy).toHaveBeenCalledWith(callback);
704+
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 0);
705+
706+
// Wait for the callback to be executed
707+
setTimeout(() => {
708+
expect(callback).toHaveBeenCalledTimes(1);
709+
queueMicrotaskSpy.mockRestore();
710+
setTimeoutSpy.mockRestore();
711+
done();
712+
}, 10);
713+
}));
714+
});
715+
664716
describe('removeAccentFromText() method', () => {
665717
it('should return a normalized string without accent', () => {
666718
const input1 = 'José';

packages/utils/src/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,15 @@ export function parseBoolean(input: any): boolean {
240240
return /(true|1)/i.test(input + '');
241241
}
242242

243+
/** use `queueMicrotask()` when available, otherwise fallback to `setTimeout` for Salesforce LWC locker service */
244+
export function queueMicrotaskOrSetTimeout(callback: () => void): void {
245+
try {
246+
queueMicrotask(callback);
247+
} catch {
248+
setTimeout(callback, 0);
249+
}
250+
}
251+
243252
/**
244253
* Remove any accents from a string by normalizing it
245254
* @param {String} text - input text

packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import {
5353
SlickGrid,
5454
unsubscribeAll,
5555
} from '@slickgrid-universal/common';
56-
import { extend } from '@slickgrid-universal/utils';
56+
import { extend, queueMicrotaskOrSetTimeout } from '@slickgrid-universal/utils';
5757
import { EventNamingStyle, EventPubSubService } from '@slickgrid-universal/event-pub-sub';
5858
import { SlickEmptyWarningComponent } from '@slickgrid-universal/empty-warning-component';
5959
import { SlickFooterComponent } from '@slickgrid-universal/custom-footer-component';
@@ -197,7 +197,7 @@ export class SlickVanillaGridBundle<TData = any> {
197197

198198
// we also need to reset/refresh the Tree Data filters because if we inserted new item(s) then it might not show up without doing this refresh
199199
// however we need to queue our process until the flat dataset is ready, so we can queue a microtask to execute the DataView refresh only after everything is ready
200-
queueMicrotask(() => {
200+
queueMicrotaskOrSetTimeout(() => {
201201
const flatDatasetLn = this.dataView?.getItemCount() ?? 0;
202202
if (flatDatasetLn > 0 && (flatDatasetLn !== prevFlatDatasetLn || !isDatasetEqual)) {
203203
this.filterService.refreshTreeDataFilters();
@@ -967,7 +967,7 @@ export class SlickVanillaGridBundle<TData = any> {
967967
const process = isExecuteCommandOnInit ? (backendApi.process?.(query) ?? null) : (backendApi.onInit?.(query) ?? null);
968968

969969
// wrap this inside a microtask to be executed at the end of the task and avoid timing issue since the gridOptions needs to be ready before running this onInit
970-
queueMicrotask(() => {
970+
queueMicrotaskOrSetTimeout(() => {
971971
const backendUtilityService = this.backendUtilityService as BackendUtilityService;
972972
// keep start time & end timestamps & return it after process execution
973973
const startTime = new Date();

0 commit comments

Comments
 (0)