mirror of
https://github.com/zen-browser/pdf.js.git
synced 2025-07-09 09:45:42 +02:00
Use A+ spec compatible promises.
This commit is contained in:
parent
b996e1b781
commit
ae1f973204
7 changed files with 240 additions and 198 deletions
278
src/util.js
278
src/util.js
|
@ -649,41 +649,129 @@ function isPDFFunction(v) {
|
|||
}
|
||||
|
||||
/**
|
||||
* 'Promise' object.
|
||||
* Each object that is stored in PDFObjects is based on a Promise object that
|
||||
* contains the status of the object and the data. There might be situations
|
||||
* where a function wants to use the value of an object, but it isn't ready at
|
||||
* that time. To get a notification, once the object is ready to be used, s.o.
|
||||
* can add a callback using the `then` method on the promise that then calls
|
||||
* the callback once the object gets resolved.
|
||||
* A promise can get resolved only once and only once the data of the promise
|
||||
* can be set. If any of these happens twice or the data is required before
|
||||
* it was set, an exception is throw.
|
||||
* The following promise implementation tries to generally implment the
|
||||
* Promise/A+ spec. Some notable differences from other promise libaries are:
|
||||
* - There currently isn't a seperate deferred and promise object.
|
||||
* - Unhandled rejections eventually show an error if they aren't handled.
|
||||
* - Progress events are supported.
|
||||
*
|
||||
* Based off of the work in:
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=810490
|
||||
*/
|
||||
var Promise = PDFJS.Promise = (function PromiseClosure() {
|
||||
var EMPTY_PROMISE = {};
|
||||
var STATUS_PENDING = 0;
|
||||
var STATUS_RESOLVED = 1;
|
||||
var STATUS_REJECTED = 2;
|
||||
var STATUS_PROGRESS = 3;
|
||||
|
||||
/**
|
||||
* If `data` is passed in this constructor, the promise is created resolved.
|
||||
* If there isn't data, it isn't resolved at the beginning.
|
||||
*/
|
||||
function Promise(name, data) {
|
||||
this.name = name;
|
||||
this.isRejected = false;
|
||||
this.error = null;
|
||||
this.exception = null;
|
||||
// If you build a promise and pass in some data it's already resolved.
|
||||
if (data !== null && data !== undefined) {
|
||||
this.isResolved = true;
|
||||
this._data = data;
|
||||
this.hasData = true;
|
||||
} else {
|
||||
this.isResolved = false;
|
||||
this._data = EMPTY_PROMISE;
|
||||
// In an attempt to avoid silent exceptions, unhandled rejections are
|
||||
// tracked and if they aren't handled in a certain amount of time an
|
||||
// error is logged.
|
||||
var REJECTION_TIMEOUT = 500;
|
||||
|
||||
var HandlerManager = {
|
||||
handlers: [],
|
||||
running: false,
|
||||
unhandledRejections: [],
|
||||
pendingRejectionCheck: false,
|
||||
|
||||
scheduleHandlers: function scheduleHandlers(promise) {
|
||||
if (promise._status == STATUS_PENDING) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handlers = this.handlers.concat(promise._handlers);
|
||||
if (promise._status !== STATUS_PROGRESS) {
|
||||
promise._handlers = [];
|
||||
}
|
||||
|
||||
if (this.running) {
|
||||
return;
|
||||
}
|
||||
this.running = true;
|
||||
|
||||
setTimeout(this.runHandlers.bind(this), 0);
|
||||
},
|
||||
|
||||
runHandlers: function runHandlers() {
|
||||
while (this.handlers.length > 0) {
|
||||
var handler = this.handlers.shift();
|
||||
|
||||
var nextStatus = handler.thisPromise._status;
|
||||
var nextValue = handler.thisPromise._value;
|
||||
|
||||
try {
|
||||
if (nextStatus === STATUS_RESOLVED) {
|
||||
if (typeof(handler.onResolve) == 'function') {
|
||||
nextValue = handler.onResolve(nextValue);
|
||||
}
|
||||
} else if (nextStatus === STATUS_PROGRESS) {
|
||||
if (typeof(handler.onProgress) === 'function') {
|
||||
nextValue = handler.onProgress(nextValue);
|
||||
}
|
||||
} else if (typeof(handler.onReject) === 'function') {
|
||||
nextValue = handler.onReject(nextValue);
|
||||
nextStatus = STATUS_RESOLVED;
|
||||
|
||||
if (handler.thisPromise._unhandledRejection) {
|
||||
this.removeUnhandeledRejection(handler.thisPromise);
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
nextStatus = STATUS_REJECTED;
|
||||
nextValue = ex;
|
||||
}
|
||||
|
||||
handler.nextPromise._updateStatus(nextStatus, nextValue);
|
||||
}
|
||||
|
||||
this.running = false;
|
||||
},
|
||||
|
||||
addUnhandledRejection: function addUnhandledRejection(promise) {
|
||||
this.unhandledRejections.push({
|
||||
promise: promise,
|
||||
time: Date.now()
|
||||
});
|
||||
this.scheduleRejectionCheck();
|
||||
},
|
||||
|
||||
removeUnhandeledRejection: function removeUnhandeledRejection(promise) {
|
||||
promise._unhandledRejection = false;
|
||||
for (var i = 0; i < this.unhandledRejections.length; i++) {
|
||||
if (this.unhandledRejections[i].promise === promise) {
|
||||
this.unhandledRejections.splice(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
scheduleRejectionCheck: function scheduleRejectionCheck() {
|
||||
if (this.pendingRejectionCheck) {
|
||||
return;
|
||||
}
|
||||
this.pendingRejectionCheck = true;
|
||||
setTimeout(function rejectionCheck() {
|
||||
this.pendingRejectionCheck = false;
|
||||
var now = Date.now();
|
||||
for (var i = 0; i < this.unhandledRejections.length; i++) {
|
||||
if (now - this.unhandledRejections[i].time > REJECTION_TIMEOUT) {
|
||||
console.error('Unhandled rejection: ' +
|
||||
this.unhandledRejections[i].promise._value);
|
||||
this.unhandledRejections.splice(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
if (this.unhandledRejections.length) {
|
||||
this.scheduleRejectionCheck();
|
||||
}
|
||||
}.bind(this), REJECTION_TIMEOUT);
|
||||
}
|
||||
this.callbacks = [];
|
||||
this.errbacks = [];
|
||||
this.progressbacks = [];
|
||||
};
|
||||
|
||||
function Promise() {
|
||||
this._status = STATUS_PENDING;
|
||||
this._handlers = [];
|
||||
}
|
||||
/**
|
||||
* Builds a promise that is resolved when all the passed in promises are
|
||||
|
@ -700,7 +788,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
|
|||
return deferred;
|
||||
}
|
||||
function reject(reason) {
|
||||
if (deferred.isRejected) {
|
||||
if (deferred._status === STATUS_REJECTED) {
|
||||
return;
|
||||
}
|
||||
results = [];
|
||||
|
@ -710,7 +798,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
|
|||
var promise = promises[i];
|
||||
promise.then((function(i) {
|
||||
return function(value) {
|
||||
if (deferred.isRejected) {
|
||||
if (deferred._status === STATUS_REJECTED) {
|
||||
return;
|
||||
}
|
||||
results[i] = value;
|
||||
|
@ -722,102 +810,68 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
|
|||
}
|
||||
return deferred;
|
||||
};
|
||||
Promise.prototype = {
|
||||
hasData: false,
|
||||
|
||||
set data(value) {
|
||||
if (value === undefined) {
|
||||
Promise.prototype = {
|
||||
_status: null,
|
||||
_value: null,
|
||||
_handlers: null,
|
||||
_unhandledRejection: null,
|
||||
|
||||
_updateStatus: function Promise__updateStatus(status, value) {
|
||||
if (this._status === STATUS_RESOLVED ||
|
||||
this._status === STATUS_REJECTED) {
|
||||
return;
|
||||
}
|
||||
if (this._data !== EMPTY_PROMISE) {
|
||||
error('Promise ' + this.name +
|
||||
': Cannot set the data of a promise twice');
|
||||
}
|
||||
this._data = value;
|
||||
this.hasData = true;
|
||||
|
||||
if (this.onDataCallback) {
|
||||
this.onDataCallback(value);
|
||||
if (status == STATUS_RESOLVED &&
|
||||
value && typeof(value.then) === 'function') {
|
||||
value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
|
||||
this._updateStatus.bind(this, STATUS_REJECTED));
|
||||
return;
|
||||
}
|
||||
|
||||
this._status = status;
|
||||
this._value = value;
|
||||
|
||||
if (status === STATUS_REJECTED && this._handlers.length === 0) {
|
||||
this._unhandledRejection = true;
|
||||
HandlerManager.addUnhandledRejection(this);
|
||||
}
|
||||
|
||||
HandlerManager.scheduleHandlers(this);
|
||||
},
|
||||
|
||||
get data() {
|
||||
if (this._data === EMPTY_PROMISE) {
|
||||
error('Promise ' + this.name + ': Cannot get data that isn\'t set');
|
||||
}
|
||||
return this._data;
|
||||
get isResolved() {
|
||||
return this._status === STATUS_RESOLVED;
|
||||
},
|
||||
|
||||
onData: function Promise_onData(callback) {
|
||||
if (this._data !== EMPTY_PROMISE) {
|
||||
callback(this._data);
|
||||
} else {
|
||||
this.onDataCallback = callback;
|
||||
}
|
||||
get isRejected() {
|
||||
return this._status === STATUS_REJECTED;
|
||||
},
|
||||
|
||||
resolve: function Promise_resolve(data) {
|
||||
if (this.isResolved) {
|
||||
error('A Promise can be resolved only once ' + this.name);
|
||||
}
|
||||
if (this.isRejected) {
|
||||
error('The Promise was already rejected ' + this.name);
|
||||
}
|
||||
|
||||
this.isResolved = true;
|
||||
this.data = (typeof data !== 'undefined') ? data : null;
|
||||
var callbacks = this.callbacks;
|
||||
|
||||
for (var i = 0, ii = callbacks.length; i < ii; i++) {
|
||||
callbacks[i].call(null, data);
|
||||
}
|
||||
resolve: function Promise_resolve(value) {
|
||||
this._updateStatus(STATUS_RESOLVED, value);
|
||||
},
|
||||
|
||||
progress: function Promise_progress(data) {
|
||||
var callbacks = this.progressbacks;
|
||||
for (var i = 0, ii = callbacks.length; i < ii; i++) {
|
||||
callbacks[i].call(null, data);
|
||||
}
|
||||
reject: function Promise_reject(reason) {
|
||||
this._updateStatus(STATUS_REJECTED, reason);
|
||||
},
|
||||
|
||||
reject: function Promise_reject(reason, exception) {
|
||||
if (this.isRejected) {
|
||||
error('A Promise can be rejected only once ' + this.name);
|
||||
}
|
||||
if (this.isResolved) {
|
||||
error('The Promise was already resolved ' + this.name);
|
||||
}
|
||||
|
||||
this.isRejected = true;
|
||||
this.error = reason || null;
|
||||
this.exception = exception || null;
|
||||
var errbacks = this.errbacks;
|
||||
|
||||
for (var i = 0, ii = errbacks.length; i < ii; i++) {
|
||||
errbacks[i].call(null, reason, exception);
|
||||
}
|
||||
notify: function Promise_notify(update) {
|
||||
this._updateStatus(STATUS_PROGRESS, update);
|
||||
},
|
||||
|
||||
then: function Promise_then(callback, errback, progressback) {
|
||||
// If the promise is already resolved, call the callback directly.
|
||||
if (this.isResolved && callback) {
|
||||
var data = this.data;
|
||||
callback.call(null, data);
|
||||
} else if (this.isRejected && errback) {
|
||||
var error = this.error;
|
||||
var exception = this.exception;
|
||||
errback.call(null, error, exception);
|
||||
} else {
|
||||
if (callback) {
|
||||
this.callbacks.push(callback);
|
||||
}
|
||||
if (errback) {
|
||||
this.errbacks.push(errback);
|
||||
}
|
||||
}
|
||||
|
||||
if (progressback)
|
||||
this.progressbacks.push(progressback);
|
||||
then: function Promise_then(onResolve, onReject, onProgress) {
|
||||
var nextPromise = new Promise();
|
||||
this._handlers.push({
|
||||
thisPromise: this,
|
||||
onResolve: onResolve,
|
||||
onReject: onReject,
|
||||
onProgress: onProgress,
|
||||
nextPromise: nextPromise
|
||||
});
|
||||
HandlerManager.scheduleHandlers(this);
|
||||
return nextPromise;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue