// Support: Firefox < 97, Chrome < 98, Safari < 15.4.
// mw.Api's use of AbortController requires the AbortSignal 'reason' property,
// which was not supported in earlier versions.
// https://caniuse.com/mdn-api_abortsignal_reason
/**
* @classdesc
* Subset of {@link AbortController} sufficient for the needs of {@link mw.Api}.
* Used by {@link mw.Api#ajax}, {@link mw.Api#get}, {@link mw.Api#post} and related methods.
*
* It may be used as a fallback on browsers that don't support DOM AbortController.
* However, it's not compliant with the spec, and can't be used as a polyfill for
* AbortController with `fetch()` or anything else.
*
* Aborting requests this way is somewhat verbose in simple cases, see
* {@link mw.Api~AbortablePromise} for an alternative style. However, it is **much** less verbose
* when chaining multiple requests and making the whole chain abortable, which would otherwise
* require carefully keeping track of the "current" promise at every step and forwarding the
* `.abort()` calls (see T346984), and it's the only style that is fully compatible with native
* promises (using `async`/`await`).
*
* @since 1.44
* @hideconstructor
* @class mw.Api~AbortController
* @extends AbortController
*
* @example
Cancelling an API request (using AbortController)
* const api = new mw.Api();
* const abort = new AbortController();
* setTimeout( function() { abort.abort(); }, 500 );
* api.get( { meta: 'userinfo' }, { signal: abort.signal } ).then( ... );
*
* @example Cancelling chained API requests
* const api = new mw.Api();
* const abort = new AbortController();
* setTimeout( function() { abort.abort(); }, 500 );
* const options = { signal: abort.signal };
* api.get( { meta: 'userinfo' }, options ).then( function ( userinfo ) {
* const name = userinfo.query.userinfo.name;
* api.get( { list: 'usercontribs', ucuser: name }, options ).then( function ( usercontribs ) {
* console.log( usercontribs.query.usercontribs );
* } );
* } ).catch( console.log );
* // => DOMException: The operation was aborted.
*
* @example Cancelling chained API requests (using await)
* const api = new mw.Api();
* const abort = new AbortController();
* setTimeout( function() { abort.abort(); }, 500 );
* const options = { signal: abort.signal };
* const userinfo = await api.get( { meta: 'userinfo' }, options );
* // throws DOMException: The operation was aborted.
* const name = userinfo.query.userinfo.name;
* const usercontribs = await api.get( { list: 'usercontribs', ucuser: name }, options );
* console.log( usercontribs.query.usercontribs );
*/
mw.Api.AbortController = function () {
/**
* @member {AbortSignal} signal
* @memberof mw.Api~AbortController#
*/
this.signal = {
aborted: false,
reason: undefined,
handlers: $.Callbacks(),
addEventListener: function ( event, handler ) {
if ( event === 'abort' ) {
this.handlers.add( handler );
}
}
};
/**
* Cancel the promises using this controller's {@link mw.Api~AbortController#signal signal},
* rejecting them with the given `reason` and stopping related async operations.
*
* @method abort
* @param {Error} reason
* @memberof mw.Api~AbortController#
*/
this.abort = function ( reason ) {
if ( reason === undefined ) {
reason = new DOMException( 'The operation was aborted.', 'AbortError' );
}
this.signal.aborted = true;
this.signal.reason = reason;
this.signal.handlers.fire();
};
};