Update Q and deprecated Promise patterns and methods #588 (#589)

This commit is contained in:
Jaifroid 2020-02-29 20:15:17 +00:00 committed by GitHub
parent cd7b4bcbbc
commit c2e6b5e275
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 219 additions and 137 deletions

View File

@ -26,8 +26,8 @@
// This uses require.js to structure javascript:
// http://requirejs.org/docs/api.html#define
define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFilesystemAccess','q'],
function($, zimArchiveLoader, util, uiUtil, cookies, abstractFilesystemAccess, q) {
define(['jquery', 'zimArchiveLoader', 'uiUtil', 'cookies','abstractFilesystemAccess','q'],
function($, zimArchiveLoader, uiUtil, cookies, abstractFilesystemAccess, Q) {
/**
* Maximum number of articles to display in a search
@ -378,7 +378,7 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
* @returns {Promise<Object>} A Promise for an object with cache attributes 'type', 'description', and 'count'
*/
function getCacheAttributes() {
return q.Promise(function (resolve, reject) {
return Q.Promise(function (resolve, reject) {
if (contentInjectionMode === 'serviceworker') {
// Create a Message Channel
var channel = new MessageChannel();
@ -858,44 +858,45 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
* Reads a remote archive with given URL, and returns the response in a Promise.
* This function is used by setRemoteArchives below, for UI tests
*
* @param url The URL of the archive to read
* @returns {Promise}
* @param {String} url The URL of the archive to read
* @returns {Promise<Blob>} A promise for the requested file (blob)
*/
function readRemoteArchive(url) {
var deferred = q.defer();
// DEV: This deferred can't be standardized to a Promise/A+ pattern (using Q) because
// IE11 is unable to scope the callbacks inside the Promise correctly. See [kiwix.js #589]
var deferred = Q.defer();
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.open("GET", url);
request.responseType = "blob";
request.onreadystatechange = function () {
if (request.readyState === XMLHttpRequest.DONE) {
if ((request.status >= 200 && request.status < 300) || request.status === 0) {
if (request.status >= 200 && request.status < 300 || request.status === 0) {
// Hack to make this look similar to a file
request.response.name = url;
deferred.resolve(request.response);
}
else {
} else {
deferred.reject("HTTP status " + request.status + " when reading " + url);
}
}
};
request.onabort = function (e) {
deferred.reject(e);
};
request.send(null);
request.onabort = request.onerror = deferred.reject;
request.send();
return deferred.promise;
}
/**
* This is used in the testing interface to inject remote archives
* @returns {Promise<Array>} A Promise for an array of archives
*/
window.setRemoteArchives = function() {
window.setRemoteArchives = function () {
var readRequests = [];
var i;
for (i = 0; i < arguments.length; i++) {
readRequests[i] = readRemoteArchive(arguments[i]);
}
return q.all(readRequests).then(function(arrayOfArchives) {
Array.prototype.slice.call(arguments).forEach(function (arg) {
readRequests.push(readRemoteArchive(arg));
});
return Q.all(readRequests).then(function (arrayOfArchives) {
setLocalArchiveFromFileList(arrayOfArchives);
}).catch(function (e) {
console.error('Unable to load remote archive(s)', e);
});
};
@ -1136,7 +1137,7 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
});
}
};
selectedArchive.getDirEntryByTitle(title).then(readFile).fail(function () {
selectedArchive.getDirEntryByTitle(title).then(readFile).catch(function () {
messagePort.postMessage({ 'action': 'giveContent', 'title': title, 'content': new UInt8Array() });
});
} else {
@ -1317,7 +1318,7 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
var mimetype = dirEntry.getMimetype();
uiUtil.feedNodeWithBlob(image, 'src', content, mimetype);
});
}).fail(function (e) {
}).catch(function (e) {
console.error("could not find DirEntry for image:" + title, e);
});
});
@ -1370,7 +1371,7 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
renderIfCSSFulfilled(fileDirEntry.url);
}
);
}).fail(function (e) {
}).catch(function (e) {
console.error("could not find DirEntry for CSS : " + title, e);
cssCount--;
renderIfCSSFulfilled();
@ -1410,7 +1411,7 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
uiUtil.feedNodeWithBlob(script, 'src', content, 'text/javascript');
});
}
}).fail(function (e) {
}).catch(function (e) {
console.error("could not find DirEntry for javascript : " + title, e);
});
});
@ -1510,7 +1511,7 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
$('#activeContent').hide();
readArticle(dirEntry);
}
}).fail(function(e) { alert("Error reading article with title " + title + " : " + e); });
}).catch(function(e) { alert("Error reading article with title " + title + " : " + e); });
}
function goToRandomArticle() {

View File

@ -1,8 +1,8 @@
// vim:ts=4:sts=4:sw=4:
/*!
*
* Copyright 2009-2012 Kris Kowal under the terms of the MIT
* license found at http://github.com/kriskowal/q/raw/master/LICENSE
* Copyright 2009-2017 Kris Kowal under the terms of the MIT
* license found at https://github.com/kriskowal/q/blob/v1/LICENSE
*
* With parts by Tyler Close
* Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
@ -55,8 +55,22 @@
}
// <script>
} else if (typeof self !== "undefined") {
self.Q = definition();
} else if (typeof window !== "undefined" || typeof self !== "undefined") {
// Prefer window over self for add-on scripts. Use self for
// non-windowed contexts.
var global = typeof window !== "undefined" ? window : self;
// Get the `window` object, save the previous Q global
// and initialize Q as a global.
var previousQ = global.Q;
global.Q = definition();
// Add a noConflict function so Q can be removed from the
// global namespace.
global.Q.noConflict = function () {
global.Q = previousQ;
return this;
};
} else {
throw new Error("This environment was not anticipated by Q. Please file a bug.");
@ -91,57 +105,67 @@ var nextTick =(function () {
var flushing = false;
var requestTick = void 0;
var isNodeJS = false;
// queue for late tasks, used by unhandled rejection tracking
var laterQueue = [];
function flush() {
/* jshint loopfunc: true */
var task, domain;
while (head.next) {
head = head.next;
var task = head.task;
task = head.task;
head.task = void 0;
var domain = head.domain;
domain = head.domain;
if (domain) {
head.domain = void 0;
domain.enter();
}
runSingle(task, domain);
try {
task();
}
while (laterQueue.length) {
task = laterQueue.pop();
runSingle(task);
}
flushing = false;
}
// runs a single function in the async queue
function runSingle(task, domain) {
try {
task();
} catch (e) {
if (isNodeJS) {
// In node, uncaught exceptions are considered fatal errors.
// Re-throw them synchronously to interrupt flushing!
} catch (e) {
if (isNodeJS) {
// In node, uncaught exceptions are considered fatal errors.
// Re-throw them synchronously to interrupt flushing!
// Ensure continuation if the uncaught exception is suppressed
// listening "uncaughtException" events (as domains does).
// Continue in next event to avoid tick recursion.
if (domain) {
domain.exit();
}
setTimeout(flush, 0);
if (domain) {
domain.enter();
}
throw e;
} else {
// In browsers, uncaught exceptions are not fatal.
// Re-throw them asynchronously to avoid slow-downs.
setTimeout(function() {
throw e;
}, 0);
// Ensure continuation if the uncaught exception is suppressed
// listening "uncaughtException" events (as domains does).
// Continue in next event to avoid tick recursion.
if (domain) {
domain.exit();
}
setTimeout(flush, 0);
if (domain) {
domain.enter();
}
}
if (domain) {
domain.exit();
throw e;
} else {
// In browsers, uncaught exceptions are not fatal.
// Re-throw them asynchronously to avoid slow-downs.
setTimeout(function () {
throw e;
}, 0);
}
}
flushing = false;
if (domain) {
domain.exit();
}
}
nextTick = function (task) {
@ -157,9 +181,16 @@ var nextTick =(function () {
}
};
if (typeof process !== "undefined" && process.nextTick) {
// Node.js before 0.9. Note that some fake-Node environments, like the
// Mocha test runner, introduce a `process` global without a `nextTick`.
if (typeof process === "object" &&
process.toString() === "[object process]" && process.nextTick) {
// Ensure Q is in a real Node environment, with a `process.nextTick`.
// To see through fake Node environments:
// * Mocha test runner - exposes a `process` global without a `nextTick`
// * Browserify - exposes a `process.nexTick` function that uses
// `setTimeout`. In this case `setImmediate` is preferred because
// it is faster. Browserify's `process.toString()` yields
// "[object Object]", while in a real Node environment
// `process.toString()` yields "[object process]".
isNodeJS = true;
requestTick = function () {
@ -203,7 +234,16 @@ var nextTick =(function () {
setTimeout(flush, 0);
};
}
// runs a task after all other tasks have been run
// this is useful for unhandled rejection tracking that needs to happen
// after all `then`d tasks have been run.
nextTick.runAfter = function (task) {
laterQueue.push(task);
if (!flushing) {
flushing = true;
requestTick();
}
};
return nextTick;
})();
@ -287,6 +327,11 @@ var object_create = Object.create || function (prototype) {
return new Type();
};
var object_defineProperty = Object.defineProperty || function (obj, prop, descriptor) {
obj[prop] = descriptor.value;
return obj;
};
var object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
var object_keys = Object.keys || function (object) {
@ -337,19 +382,20 @@ function makeStackTraceLong(error, promise) {
promise.stack &&
typeof error === "object" &&
error !== null &&
error.stack &&
error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1
error.stack
) {
var stacks = [];
for (var p = promise; !!p; p = p.source) {
if (p.stack) {
if (p.stack && (!error.__minimumStackCounter__ || error.__minimumStackCounter__ > p.stackCounter)) {
object_defineProperty(error, "__minimumStackCounter__", {value: p.stackCounter, configurable: true});
stacks.unshift(p.stack);
}
}
stacks.unshift(error.stack);
var concatedStacks = stacks.join("\n" + STACK_JUMP_SEPARATOR + "\n");
error.stack = filterStackString(concatedStacks);
var stack = filterStackString(concatedStacks);
object_defineProperty(error, "stack", {value: stack, configurable: true});
}
}
@ -476,6 +522,14 @@ Q.nextTick = nextTick;
*/
Q.longStackSupport = false;
/**
* The counter is used to determine the stopping point for building
* long stack traces. In makeStackTraceLong we walk backwards through
* the linked list of promises, only stacks which were created before
* the rejection are concatenated.
*/
var longStackCounter = 1;
// enable long stacks if Q_DEBUG is set
if (typeof process === "object" && process && process.env && process.env.Q_DEBUG) {
Q.longStackSupport = true;
@ -548,6 +602,7 @@ function defer() {
// At the same time, cut off the first line; it's always just
// "[object Promise]\n", as per the `toString`.
promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1);
promise.stackCounter = longStackCounter++;
}
}
@ -557,7 +612,12 @@ function defer() {
function become(newPromise) {
resolvedPromise = newPromise;
promise.source = newPromise;
if (Q.longStackSupport && hasStacks) {
// Only hold a reference to the new promise if long stacks
// are enabled to reduce memory usage
promise.source = newPromise;
}
array_reduce(messages, function (undefined, message) {
Q.nextTick(function () {
@ -685,7 +745,7 @@ Promise.prototype.join = function (that) {
// TODO: "===" should be Object.is or equiv
return x;
} else {
throw new Error("Can't join: not the same: " + x + " " + y);
throw new Error("Q can't join: not the same: " + x + " " + y);
}
});
};
@ -697,9 +757,9 @@ Promise.prototype.join = function (that) {
*/
Q.race = race;
function race(answerPs) {
return promise(function(resolve, reject) {
return promise(function (resolve, reject) {
// Switch to this once we can assume at least ES5
// answerPs.forEach(function(answerP) {
// answerPs.forEach(function (answerP) {
// Q(answerP).then(resolve, reject);
// });
// Use this in the meantime
@ -997,6 +1057,7 @@ Promise.prototype.isRejected = function () {
// shimmed environments, this would naturally be a `Set`.
var unhandledReasons = [];
var unhandledRejections = [];
var reportedUnhandledRejections = [];
var trackUnhandledRejections = true;
function resetUnhandledRejections() {
@ -1012,6 +1073,14 @@ function trackRejection(promise, reason) {
if (!trackUnhandledRejections) {
return;
}
if (typeof process === "object" && typeof process.emit === "function") {
Q.nextTick.runAfter(function () {
if (array_indexOf(unhandledRejections, promise) !== -1) {
process.emit("unhandledRejection", reason, promise);
reportedUnhandledRejections.push(promise);
}
});
}
unhandledRejections.push(promise);
if (reason && typeof reason.stack !== "undefined") {
@ -1028,6 +1097,15 @@ function untrackRejection(promise) {
var at = array_indexOf(unhandledRejections, promise);
if (at !== -1) {
if (typeof process === "object" && typeof process.emit === "function") {
Q.nextTick.runAfter(function () {
var atReport = array_indexOf(reportedUnhandledRejections, promise);
if (atReport !== -1) {
process.emit("rejectionHandled", unhandledReasons[at], promise);
reportedUnhandledRejections.splice(atReport, 1);
}
});
}
unhandledRejections.splice(at, 1);
unhandledReasons.splice(at, 1);
}
@ -1555,7 +1633,7 @@ function any(promises) {
var deferred = Q.defer();
var pendingCount = 0;
array_reduce(promises, function(prev, current, index) {
array_reduce(promises, function (prev, current, index) {
var promise = promises[index];
pendingCount++;
@ -1564,13 +1642,15 @@ function any(promises) {
function onFulfilled(result) {
deferred.resolve(result);
}
function onRejected() {
function onRejected(err) {
pendingCount--;
if (pendingCount === 0) {
deferred.reject(new Error(
"Can't get fulfillment value from any promise, all " +
"promises were rejected."
));
var rejection = err || new Error("" + err);
rejection.message = ("Q can't get fulfillment value from any promise, all " +
"promises were rejected. Last error message: " + rejection.message);
deferred.reject(rejection);
}
}
function onProgress(progress) {
@ -1584,7 +1664,7 @@ function any(promises) {
return deferred.promise;
}
Promise.prototype.any = function() {
Promise.prototype.any = function () {
return any(this);
};
@ -1694,6 +1774,9 @@ Q["finally"] = function (object, callback) {
Promise.prototype.fin = // XXX legacy
Promise.prototype["finally"] = function (callback) {
if (!callback || typeof callback.apply !== "function") {
throw new Error("Q can't apply finally callback");
}
callback = Q(callback);
return this.then(function (value) {
return callback.fcall().then(function () {
@ -1857,6 +1940,9 @@ Promise.prototype.nfcall = function (/*...args*/) {
*/
Q.nfbind =
Q.denodeify = function (callback /*...args*/) {
if (callback === undefined) {
throw new Error("Q can't wrap an undefined function");
}
var baseArgs = array_slice(arguments, 1);
return function () {
var nodeArgs = baseArgs.concat(array_slice(arguments));
@ -1978,6 +2064,10 @@ Promise.prototype.nodeify = function (nodeback) {
}
};
Q.noConflict = function() {
throw new Error("Q.noConflict only works when Q is used as a global");
};
// All code before this point will be filtered from stack traces.
var qEndingLine = captureLine();

View File

@ -20,7 +20,7 @@
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
*/
'use strict';
define(['q'], function(q) {
define(['q'], function(Q) {
/**
* Utility function : return true if the given string ends with the suffix
@ -180,23 +180,21 @@ define(['q'], function(q) {
/**
* Reads a Uint8Array from the given file starting at byte offset begin and
* for given size.
* @param {File} file
* @param {Integer} begin
* @param {Integer} size
* @returns {Promise} Promise
* for given size
* @param {File} file The file object to be read
* @param {Integer} begin The offset in <File> at which to begin reading
* @param {Integer} size The number of bytes to read
* @returns {Promise<Uint8Array>} A Promise for an array buffer with the read data
*/
function readFileSlice(file, begin, size) {
var deferred = q.defer();
var reader = new FileReader();
reader.onload = function(e) {
deferred.resolve(new Uint8Array(e.target.result));
};
reader.onerror = reader.onabort = function(e) {
deferred.reject(e);
};
reader.readAsArrayBuffer(file.slice(begin, begin + size));
return deferred.promise;
return Q.Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function (e) {
resolve(new Uint8Array(e.target.result));
};
reader.onerror = reader.onabort = reject;
reader.readAsArrayBuffer(file.slice(begin, begin + size));
});
}
/**

View File

@ -20,7 +20,7 @@
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
*/
'use strict';
define(['q'], function(q) {
define(['q'], function(Q) {
var xzdec = Module; //@todo including via requirejs seems to not work
xzdec._init();
@ -78,29 +78,25 @@ define(['q'], function(q) {
};
/**
* Read length bytes, offset into the decompressed stream.
* This function ensures that only one decompression runs at a time.
*
* @param {Integer} offset
* @param {Integer} length
* Reads stream of data from file offset for length of bytes to send to the decompresor
* This function ensures that only one decompression runs at a time
* @param {Integer} offset The file offset at which to begin reading compressed data
* @param {Integer} length The amount of data to read
* @returns {Promise} A Promise for the read data
*/
Decompressor.prototype.readSliceSingleThread = function(offset, length) {
Decompressor.prototype.readSliceSingleThread = function (offset, length) {
if (!busy) {
return this.readSlice(offset, length);
}
else {
} else {
// The decompressor is already in progress.
// To avoid using too much memory, we wait until it has finished
// before using it for another decompression.
// Inspired by https://codereview.stackexchange.com/questions/145563/angularjs-recursive-function-call-with-timeout
// before using it for another decompression
var that = this;
var deferred = q.defer();
setTimeout(function(){
that.readSliceSingleThread(offset, length).then(deferred.resolve, deferred.reject);
}, DELAY_WAITING_IDLE_DECOMPRESSOR);
return deferred.promise;
return Q.Promise(function (resolve, reject) {
setTimeout(function () {
that.readSliceSingleThread(offset, length).then(resolve, reject);
}, DELAY_WAITING_IDLE_DECOMPRESSOR);
});
}
};
@ -150,8 +146,10 @@ define(['q'], function(q) {
* @returns {Promise}
*/
Decompressor.prototype._fillInBufferIfNeeded = function() {
if (!xzdec._input_empty(this._decHandle))
return q.when(0);
if (!xzdec._input_empty(this._decHandle)) {
// DEV: When converting to Promise/A+, use Promise.resolve(0) here
return Q.when(0);
}
var that = this;
return this._reader(this._inStreamPos, this._chunkSize).then(function(data) {
if (data.length > that._chunkSize)

View File

@ -299,7 +299,7 @@ define(['zimfile', 'zimDirEntry', 'util', 'utf8'],
callback(data);
});
}
}).fail(function (e) {
}).catch(function (e) {
console.warn("Metadata with key " + key + " not found in the archive", e);
callback();
});

View File

@ -214,7 +214,7 @@ define(['xzdec_wrapper', 'util', 'utf8', 'q', 'zimDirEntry'], function(xz, util,
* and is stored as ZIMFile.mimeTypes
*
* @param {File} file The ZIM file (or first file in array of files) from which the MIME type list
* is to be extracted
* is to be extracted
* @param {Integer} mimeListPos The offset in <file> at which the MIME type list is found
* @param {Integer} urlPtrPos The offset of the byte after the end of the MIME type list in <file>
* @returns {Promise} A promise for the MIME Type list as a Map
@ -222,13 +222,13 @@ define(['xzdec_wrapper', 'util', 'utf8', 'q', 'zimDirEntry'], function(xz, util,
function readMimetypeMap(file, mimeListPos, urlPtrPos) {
var typeMap = new Map;
var size = urlPtrPos - mimeListPos;
return util.readFileSlice(file, mimeListPos, size).then(function(data) {
return util.readFileSlice(file, mimeListPos, size).then(function (data) {
if (data.subarray) {
var i = 0;
var pos = -1;
var mimeString;
while (pos < size) {
pos++;
pos++;
mimeString = utf8.parse(data.subarray(pos), true);
// If the parsed data is an empty string, we have reached the end of the MIME type list, so break
if (!mimeString) break;
@ -241,35 +241,30 @@ define(['xzdec_wrapper', 'util', 'utf8', 'q', 'zimDirEntry'], function(xz, util,
}
}
return typeMap;
}).fail(function(err) {
}).catch(function (err) {
console.error('Unable to read MIME type list', err);
return new Map;
});
}
return {
/**
*
* @param {Array.<File>} fileArray
* @returns {Promise}
* @param {Array.<File>} fileArray An array of picked archive files
* @returns {Promise<Object>} A Promise for the ZimFile Object
*/
fromFileArray: function(fileArray) {
fromFileArray: function (fileArray) {
// Array of blob objects should be sorted by their name property
fileArray.sort(function(a, b) {
var nameA = a.name.toUpperCase();
var nameB = b.name.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
fileArray.sort(function (a, b) {
var nameA = a.name.toUpperCase();
var nameB = b.name.toUpperCase();
if (nameA < nameB) return -1;
if (nameA > nameB) return 1;
return 0;
});
return util.readFileSlice(fileArray[0], 0, 80).then(function(header) {
return util.readFileSlice(fileArray[0], 0, 80).then(function (header) {
var mimeListPos = readInt(header, 56, 8);
var urlPtrPos = readInt(header, 32, 8);
return readMimetypeMap(fileArray[0], mimeListPos, urlPtrPos).then(function(data) {
return readMimetypeMap(fileArray[0], mimeListPos, urlPtrPos).then(function (data) {
var zf = new ZIMFile(fileArray);
zf.articleCount = readInt(header, 24, 4);
zf.clusterCount = readInt(header, 28, 4);