UNPKG

@wordpress/priority-queue

Version:
186 lines (167 loc) 5.06 kB
/** * Internal dependencies */ import requestIdleCallback from './request-idle-callback'; /** * Enqueued callback to invoke once idle time permits. */ export type WPPriorityQueueCallback = VoidFunction; /** * An object used to associate callbacks in a particular context grouping. */ export type WPPriorityQueueContext = object; /** * Interface for the priority queue instance. */ export interface WPPriorityQueue { /** * Add a callback to the queue for a given context. */ add: ( element: WPPriorityQueueContext, item: WPPriorityQueueCallback ) => void; /** * Flush and run the callback for a given context immediately. * @return true if a callback was run, false otherwise. */ flush: ( element: WPPriorityQueueContext ) => boolean; /** * Cancel (remove) the callback for a given context without running it. * @return true if a callback was cancelled, false otherwise. */ cancel: ( element: WPPriorityQueueContext ) => boolean; /** * Reset the entire queue, clearing pending callbacks. */ reset: VoidFunction; } /** * Creates a context-aware queue that only executes * the last task of a given context. * * @example *```js * import { createQueue } from '@wordpress/priority-queue'; * * const queue = createQueue(); * * // Context objects. * const ctx1 = {}; * const ctx2 = {}; * * // For a given context in the queue, only the last callback is executed. * queue.add( ctx1, () => console.log( 'This will be printed first' ) ); * queue.add( ctx2, () => console.log( 'This won\'t be printed' ) ); * queue.add( ctx2, () => console.log( 'This will be printed second' ) ); *``` * * @return {WPPriorityQueue} Queue object with `add`, `flush` and `reset` methods. */ export const createQueue = (): WPPriorityQueue => { const waitingList = new Map< WPPriorityQueueContext, WPPriorityQueueCallback >(); let isRunning = false; /** * Callback to process as much queue as time permits. * * Map Iteration follows the original insertion order. This means that here * we can iterate the queue and know that the first contexts which were * added will be run first. On the other hand, if anyone adds a new callback * for an existing context it will supplant the previously-set callback for * that context because we reassigned that map key's value. * * In the case that a callback adds a new callback to its own context then * the callback it adds will appear at the end of the iteration and will be * run only after all other existing contexts have finished executing. * * @param {IdleDeadline|number} deadline Idle callback deadline object, or * animation frame timestamp. */ const runWaitingList = ( deadline: IdleDeadline | number ): void => { for ( const [ nextElement, callback ] of waitingList ) { waitingList.delete( nextElement ); callback(); if ( 'number' === typeof deadline || deadline.timeRemaining() <= 0 ) { break; } } if ( waitingList.size === 0 ) { isRunning = false; return; } requestIdleCallback( runWaitingList ); }; /** * Add a callback to the queue for a given context. * * If errors with undefined callbacks are encountered double check that * all of your useSelect calls have the right dependencies set correctly * in their second parameter. Missing dependencies can cause unexpected * loops and race conditions in the queue. * * @param {WPPriorityQueueContext} element Context object. * @param {WPPriorityQueueCallback} item Callback function. */ const add: WPPriorityQueue[ 'add' ] = ( element: WPPriorityQueueContext, item: WPPriorityQueueCallback ) => { waitingList.set( element, item ); if ( ! isRunning ) { isRunning = true; requestIdleCallback( runWaitingList ); } }; /** * Flushes queue for a given context, returning true if the flush was * performed, or false if there is no queue for the given context. * * @param {WPPriorityQueueContext} element Context object. * * @return {boolean} Whether flush was performed. */ const flush: WPPriorityQueue[ 'flush' ] = ( element: WPPriorityQueueContext ) => { const callback = waitingList.get( element ); if ( undefined === callback ) { return false; } waitingList.delete( element ); callback(); return true; }; /** * Clears the queue for a given context, cancelling the callbacks without * executing them. Returns `true` if there were scheduled callbacks to cancel, * or `false` if there was is no queue for the given context. * * @param {WPPriorityQueueContext} element Context object. * * @return {boolean} Whether any callbacks got cancelled. */ const cancel: WPPriorityQueue[ 'cancel' ] = ( element: WPPriorityQueueContext ) => { return waitingList.delete( element ); }; /** * Reset the queue without running the pending callbacks. */ const reset: WPPriorityQueue[ 'reset' ] = () => { waitingList.clear(); isRunning = false; }; return { add, flush, cancel, reset, }; };