resources/perf.webkit.org/public/v3/async-task.js (132 lines of code) (raw):

class AsyncTask { constructor(method, args) { this._method = method; this._args = args; } execute() { if (!(this._method in Statistics)) throw `${this._method} is not a valid method of Statistics`; AsyncTask._asyncMessageId++; var startTime = Date.now(); var method = this._method; var args = this._args; return new Promise(function (resolve, reject) { AsyncTaskWorker.waitForAvailableWorker(function (worker) { worker.sendTask({id: AsyncTask._asyncMessageId, method: method, args: args}).then(function (data) { var startLatency = data.workerStartTime - startTime; var totalTime = Date.now() - startTime; var callback = data.status == 'resolve' ? resolve : reject; callback({result: data.result, workerId: worker.id(), startLatency: startLatency, totalTime: totalTime, workerTime: data.workerTime}); }); }); }); } static isAvailable() { return typeof Worker !== 'undefined'; } } AsyncTask._asyncMessageId = 0; class AsyncTaskWorker { // Takes a callback instead of returning a promise because a worker can become unavailable before the end of the current microtask. static waitForAvailableWorker(callback) { var worker = this._makeWorkerEventuallyAvailable(); if (worker) callback(worker); this._queue.push(callback); } static _makeWorkerEventuallyAvailable() { var worker = this._findAvailableWorker(); if (worker) return worker; var canStartMoreWorker = this._workerSet.size < this._maxWorkerCount; if (!canStartMoreWorker) return null; if (this._latestStartTime > Date.now() - 50) { setTimeout(function () { var worker = AsyncTaskWorker._findAvailableWorker(); if (worker) AsyncTaskWorker._queue.pop()(worker); }, 50); return null; } return new AsyncTaskWorker; } static _findAvailableWorker() { for (var worker of this._workerSet) { if (!worker._currentTaskId) return worker; } return null; } constructor() { this._webWorker = new Worker('async-task.js'); this._webWorker.onmessage = this._didRecieveMessage.bind(this); this._id = AsyncTaskWorker._workerId; this._startTime = Date.now(); this._currentTaskId = null; this._callback = null; AsyncTaskWorker._latestStartTime = this._startTime; AsyncTaskWorker._workerId++; AsyncTaskWorker._workerSet.add(this); } id() { return this._id; } sendTask(task) { console.assert(!this._currentTaskId); console.assert(task.id); var self = this; this._currentTaskId = task.id; return new Promise(function (resolve) { self._webWorker.postMessage(task); self._callback = resolve; }); } _didRecieveMessage(event) { var callback = this._callback; console.assert(this._currentTaskId); this._currentTaskId = null; this._callback = null; if (AsyncTaskWorker._queue.length) AsyncTaskWorker._queue.pop()(this); else { var self = this; setTimeout(function () { if (self._currentTaskId == null) AsyncTaskWorker._workerSet.delete(self); }, 1000); } callback(event.data); } static workerDidRecieveMessage(event) { var data = event.data; var id = data.id; var method = Statistics[data.method]; var startTime = Date.now(); try { var returnValue = method.apply(Statistics, data.args); postMessage({'id': id, 'status': 'resolve', 'result': returnValue, 'workerStartTime': startTime, 'workerTime': Date.now() - startTime}); } catch (error) { postMessage({'id': id, 'status': 'reject', 'result': error.toString(), 'workerStartTime': startTime, 'workerTime': Date.now() - startTime}); throw error; } } } AsyncTaskWorker._maxWorkerCount = typeof navigator != 'undefined' && 'hardwareConcurrency' in navigator ? Math.max(1, navigator.hardwareConcurrency - 1) : 1; AsyncTaskWorker._workerSet = new Set; AsyncTaskWorker._queue = []; AsyncTaskWorker._workerId = 1; AsyncTaskWorker._latestStartTime = 0; if (typeof module == 'undefined' && typeof window == 'undefined' && typeof importScripts != 'undefined') { // Inside a worker importScripts('/shared/statistics.js'); onmessage = AsyncTaskWorker.workerDidRecieveMessage.bind(AsyncTaskWorker); } if (typeof module != 'undefined') module.exports.AsyncTask = AsyncTask;