Abort pending requests

Ensure all requests are abortable promises, and abort if
a new, conflicting request is made.

Change-Id: Ie05142f6da8cba6dde4f73c1b22960b726af4764
This commit is contained in:
Ed Sanders 2016-08-15 11:15:30 -07:00
parent f6a44f43fe
commit 6997f135db
4 changed files with 147 additions and 115 deletions

View file

@ -14,19 +14,27 @@
* Fetches a batch of revision data, including a gender setting for users who edited the revision * Fetches a batch of revision data, including a gender setting for users who edited the revision
* *
* @param {string} pageName * @param {string} pageName
* @param {Object} options - Options containing callbacks for `success` and `error` as well as * @param {Object} options Options
* optional fields for: `dir (defaults to `older`), `limit` (defaults to 500), `startId`, `endId`, * @param {string} [options.dir='older'] Sort direction
* `knownUserGenders` * @param {number} [options.limit=500] Result limit
* @param {number} [options.startId] Start ID
* @param {number} [options.endId] End ID
* @param {Object} [options.knownUserGenders] Known user genders
*/ */
fetchRevisionData: function ( pageName, options ) { fetchRevisionData: function ( pageName, options ) {
var self = this; var xhr, userXhr,
this.fetchRevisions( pageName, options ) deferred = $.Deferred(),
self = this;
options = options || {};
xhr = this.fetchRevisions( pageName, options )
.done( function ( data ) { .done( function ( data ) {
var revs = data.query.pages[ 0 ].revisions, var revs = data.query.pages[ 0 ].revisions,
/*jshint -W024 */ /*jshint -W024 */
revContinue = data.continue, revContinue = data.continue,
/*jshint +W024 */ /*jshint +W024 */
genderData = typeof options.knownUserGenders !== 'undefined' ? options.knownUserGenders : {}, genderData = options.knownUserGenders || {},
userNames; userNames;
if ( !revs ) { if ( !revs ) {
@ -35,7 +43,7 @@
userNames = self.getUserNames( revs, genderData ); userNames = self.getUserNames( revs, genderData );
self.fetchUserGenderData( userNames ) userXhr = self.fetchUserGenderData( userNames )
.done( function ( data ) { .done( function ( data ) {
var users = typeof data !== 'undefined' ? data.query.users : []; var users = typeof data !== 'undefined' ? data.query.users : [];
@ -49,34 +57,49 @@
} }
} ); } );
options.success( { revisions: revs, 'continue': revContinue } ); deferred.resolve( { revisions: revs, 'continue': revContinue } );
} ) } )
.fail( options.error ); .fail( deferred.reject );
} ) } )
.fail( options.error ); .fail( deferred.reject );
return deferred.promise( {
abort: function () {
xhr.abort();
if ( userXhr ) {
userXhr.abort();
}
}
} );
}, },
/** /**
* Fetches up to 500 revisions at a time * Fetches up to 500 revisions at a time
* *
* @param {string} pageName * @param {string} pageName
* @param {Object} options object containing optional options, fields: `dir` (defaults to `older`), * @param {Object} [options] Options
* `limit` (defaults to 500), `startId`, `endId` * @param {string} [options.dir='older'] Sort direction
* @return {jQuery} * @param {number} [options.limit=500] Result limit
* @param {number} [options.startId] Start ID
* @param {number} [options.endId] End ID
* @return {jQuery.jqXHR}
*/ */
fetchRevisions: function ( pageName, options ) { fetchRevisions: function ( pageName, options ) {
var dir = options.dir !== undefined ? options.dir : 'older', var dir, data;
data = {
action: 'query', options = options || {};
prop: 'revisions', dir = options.dir !== undefined ? options.dir : 'older';
format: 'json', data = {
rvprop: 'ids|timestamp|user|comment|parsedcomment|size|flags', action: 'query',
titles: pageName, prop: 'revisions',
formatversion: 2, format: 'json',
'continue': '', rvprop: 'ids|timestamp|user|comment|parsedcomment|size|flags',
rvlimit: 500, titles: pageName,
rvdir: dir formatversion: 2,
}; 'continue': '',
rvlimit: 500,
rvdir: dir
};
if ( options.startId !== undefined ) { if ( options.startId !== undefined ) {
data.rvstartid = options.startId; data.rvstartid = options.startId;
@ -98,7 +121,7 @@
* Fetches gender data for up to 500 user names * Fetches gender data for up to 500 user names
* *
* @param {string[]} users * @param {string[]} users
* @return {jQuery} * @return {jQuery.jqXHR}
*/ */
fetchUserGenderData: function ( users ) { fetchUserGenderData: function ( users ) {
if ( users.length === 0 ) { if ( users.length === 0 ) {
@ -118,7 +141,7 @@
}, },
/** /**
* @param {Array} revs * @param {Object[]} revs
* @param {Object} knownUserGenders * @param {Object} knownUserGenders
* @return {string[]} * @return {string[]}
*/ */
@ -132,7 +155,7 @@
}, },
/** /**
* @param {Array} data * @param {Object[]} data
* @return {Object} * @return {Object}
*/ */
getUserGenderData: function ( data ) { getUserGenderData: function ( data ) {

View file

@ -5,6 +5,7 @@
* @constructor * @constructor
*/ */
var DiffPage = function () { var DiffPage = function () {
this.lastRequest = null;
}; };
$.extend( DiffPage.prototype, { $.extend( DiffPage.prototype, {
@ -13,45 +14,58 @@
* *
* @param {number} revId1 * @param {number} revId1
* @param {number} revId2 * @param {number} revId2
* @param {number} [retryAttempt=0]
*/ */
refresh: function ( revId1, revId2 ) { refresh: function ( revId1, revId2, retryAttempt ) {
var data = { var self = this,
diff: Math.max( revId1, revId2 ), retryLimit = 2,
oldid: Math.min( revId1, revId2 ) data = {
}, diff: Math.max( revId1, revId2 ),
oldid: Math.min( revId1, revId2 )
},
params = this.getExtraDiffPageParams(); params = this.getExtraDiffPageParams();
retryAttempt = retryAttempt || 0;
if ( Object.keys( params ).length > 0 ) { if ( Object.keys( params ).length > 0 ) {
$.extend( data, params ); $.extend( data, params );
} }
if ( this.lastRequest ) {
this.lastRequest.abort();
}
$( 'table.diff[data-mw="interface"]' ) $( 'table.diff[data-mw="interface"]' )
.append( $( '<tr>' ) ) .append( $( '<tr>' ) )
.append( $( '<td>' ) ) .append( $( '<td>' ) )
.append( $( '<div>' ).addClass( 'mw-revslider-darkness' ) ); .append( $( '<div>' ).addClass( 'mw-revslider-darkness' ) );
$.ajax( {
this.lastRequest = $.ajax( {
url: mw.util.wikiScript( 'index' ), url: mw.util.wikiScript( 'index' ),
data: data, data: data,
tryCount: 0, tryCount: 0
retryLimit: 2, } );
success: function ( data ) { // Don't chain, so lastRequest is a jQuery.jqXHR object
var $container = $( '.mw-revslider-container' ), this.lastRequest.then( function ( data ) {
$contentText = $( '#mw-content-text' ), var $data,
scrollLeft = $container.find( '.mw-revslider-revisions-container' ).scrollLeft(); $container = $( '.mw-revslider-container' ),
$contentText = $( '#mw-content-text' ),
scrollLeft = $container.find( '.mw-revslider-revisions-container' ).scrollLeft();
data = $( data ); $data = $( data );
data.find( '.mw-revslider-container' ) $data.find( '.mw-revslider-container' ).replaceWith( $container );
.replaceWith( $container ); $contentText.html( $data.find( '#mw-content-text' ) )
$contentText.html( data.find( '#mw-content-text' ) ) .find( '.mw-revslider-revisions-container' ).scrollLeft( scrollLeft );
.find( '.mw-revslider-revisions-container' ).scrollLeft( scrollLeft );
mw.hook( 'wikipage.content' ).fire( $contentText ); mw.hook( 'wikipage.content' ).fire( $contentText );
}, }, function ( xhr ) {
error: function ( err ) { $( 'table.diff[data-mw="interface"] .mw-revslider-darkness' ).remove();
if ( xhr.statusText !== 'abort' ) {
this.tryCount++; this.tryCount++;
console.log( err );
mw.track( 'counter.MediaWiki.RevisionSlider.error.refresh' ); mw.track( 'counter.MediaWiki.RevisionSlider.error.refresh' );
if ( this.tryCount <= this.retryLimit ) { if ( retryAttempt <= retryLimit ) {
console.log( 'Retrying request' ); console.log( 'Retrying request' );
$.ajax( this ); self.refresh( revId1, revId2, retryAttempt + 1 );
} }
// TODO notify the user that we failed to update the diff? // TODO notify the user that we failed to update the diff?
// This could also attempt to reload the page with the correct diff loaded without ajax? // This could also attempt to reload the page with the correct diff loaded without ajax?

View file

@ -586,22 +586,21 @@
startId: revisions[ revisions.length - 1 ].getId(), startId: revisions[ revisions.length - 1 ].getId(),
dir: 'newer', dir: 'newer',
limit: revisionCount + 1, limit: revisionCount + 1,
knownUserGenders: this.slider.getRevisions().getUserGenders(), knownUserGenders: this.slider.getRevisions().getUserGenders()
success: function ( data ) { } ).then( function ( data ) {
revs = data.revisions.slice( 1 ); revs = data.revisions.slice( 1 );
if ( revs.length === 0 ) { if ( revs.length === 0 ) {
self.noMoreNewerRevisions = true; self.noMoreNewerRevisions = true;
return; return;
}
self.addRevisionsAtEnd( $slider, revs );
/*jshint -W024 */
if ( data.continue === undefined ) {
self.noMoreNewerRevisions = true;
}
/*jshint +W024 */
} }
self.addRevisionsAtEnd( $slider, revs );
/*jshint -W024 */
if ( data.continue === undefined ) {
self.noMoreNewerRevisions = true;
}
/*jshint +W024 */
} ); } );
}, },
@ -624,26 +623,25 @@
// fetch an extra revision if there are more older revision than the current "window", // fetch an extra revision if there are more older revision than the current "window",
// this makes it possible to correctly set a size of the bar related to the oldest revision to add // this makes it possible to correctly set a size of the bar related to the oldest revision to add
limit: revisionCount + 2, limit: revisionCount + 2,
knownUserGenders: this.slider.getRevisions().getUserGenders(), knownUserGenders: this.slider.getRevisions().getUserGenders()
success: function ( data ) { } ).then( function ( data ) {
revs = data.revisions.slice( 1 ).reverse(); revs = data.revisions.slice( 1 ).reverse();
if ( revs.length === 0 ) { if ( revs.length === 0 ) {
self.noMoreOlderRevisions = true; self.noMoreOlderRevisions = true;
return; return;
}
if ( revs.length === revisionCount + 1 ) {
precedingRevisionSize = revs[ 0 ].size;
revs = revs.slice( 1 );
}
self.addRevisionsAtStart( $slider, revs, precedingRevisionSize );
/*jshint -W024 */
if ( data.continue === undefined ) {
self.noMoreOlderRevisions = true;
}
/*jshint +W024 */
} }
if ( revs.length === revisionCount + 1 ) {
precedingRevisionSize = revs[ 0 ].size;
revs = revs.slice( 1 );
}
self.addRevisionsAtStart( $slider, revs, precedingRevisionSize );
/*jshint -W024 */
if ( data.continue === undefined ) {
self.noMoreOlderRevisions = true;
}
/*jshint +W024 */
} ); } );
}, },

View file

@ -13,48 +13,45 @@
api.fetchRevisionData( mw.config.get( 'wgPageName' ), { api.fetchRevisionData( mw.config.get( 'wgPageName' ), {
startId: mw.config.values.extRevisionSliderNewRev, startId: mw.config.values.extRevisionSliderNewRev,
limit: mw.libs.revisionSlider.calculateRevisionsPerWindow( 160, 16 ), limit: mw.libs.revisionSlider.calculateRevisionsPerWindow( 160, 16 )
} ).then( function ( data ) {
var revs,
revisionList,
$container,
slider;
success: function ( data ) { mw.track( 'timing.MediaWiki.RevisionSlider.timing.initFetchRevisionData', mw.now() - startTime );
var revs,
revisionList,
$container,
slider;
mw.track( 'timing.MediaWiki.RevisionSlider.timing.initFetchRevisionData', mw.now() - startTime ); try {
revs = data.revisions;
revs.reverse();
try { revisionList = new mw.libs.revisionSlider.RevisionList( mw.libs.revisionSlider.makeRevisions( revs ) );
revs = data.revisions;
revs.reverse();
revisionList = new mw.libs.revisionSlider.RevisionList( mw.libs.revisionSlider.makeRevisions( revs ) ); $container = $( '.mw-revslider-slider-wrapper' );
slider = new mw.libs.revisionSlider.Slider( revisionList );
slider.getView().render( $container );
$container = $( '.mw-revslider-slider-wrapper' ); if ( !mw.user.options.get( 'userjs-revslider-hidehelp' ) ) {
slider = new mw.libs.revisionSlider.Slider( revisionList ); mw.libs.revisionSlider.HelpDialog.show();
slider.getView().render( $container ); ( new mw.Api() ).saveOption( 'userjs-revslider-hidehelp', true );
if ( !mw.user.options.get( 'userjs-revslider-hidehelp' ) ) {
mw.libs.revisionSlider.HelpDialog.show();
( new mw.Api() ).saveOption( 'userjs-revslider-hidehelp', true );
}
$( '.mw-revslider-placeholder' ).remove();
mw.track( 'timing.MediaWiki.RevisionSlider.timing.init', mw.now() - startTime );
} catch ( err ) {
$( '.mw-revslider-placeholder' )
.text( mw.message( 'revisionslider-loading-failed' ).text() );
console.log( err );
mw.track( 'counter.MediaWiki.RevisionSlider.error.init' );
} }
initialized = true; $( '.mw-revslider-placeholder' ).remove();
}, mw.track( 'timing.MediaWiki.RevisionSlider.timing.init', mw.now() - startTime );
error: function ( err ) { } catch ( err ) {
$( '.mw-revslider-placeholder' ) $( '.mw-revslider-placeholder' )
.text( mw.message( 'revisionslider-loading-failed' ).text() ); .text( mw.message( 'revisionslider-loading-failed' ).text() );
console.log( err ); console.log( err );
mw.track( 'counter.MediaWiki.RevisionSlider.error.init' ); mw.track( 'counter.MediaWiki.RevisionSlider.error.init' );
} }
initialized = true;
}, function ( err ) {
$( '.mw-revslider-placeholder' )
.text( mw.message( 'revisionslider-loading-failed' ).text() );
console.log( err );
mw.track( 'counter.MediaWiki.RevisionSlider.error.init' );
} ); } );
}; };