UNPKG

dexie

Version:

A Minimalistic Wrapper for IndexedDB

1,286 lines (1,278 loc) 208 kB
/* * Dexie.js - a minimalistic wrapper for IndexedDB * =============================================== * * By David Fahlander, david.fahlander@gmail.com * * Version 2.0.4, Fri May 25 2018 * * http://dexie.org * * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/ */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.Dexie = factory()); }(this, (function () { 'use strict'; var keys = Object.keys; var isArray = Array.isArray; var _global = typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : global; function extend(obj, extension) { if (typeof extension !== 'object') return obj; keys(extension).forEach(function (key) { obj[key] = extension[key]; }); return obj; } var getProto = Object.getPrototypeOf; var _hasOwn = {}.hasOwnProperty; function hasOwn(obj, prop) { return _hasOwn.call(obj, prop); } function props(proto, extension) { if (typeof extension === 'function') extension = extension(getProto(proto)); keys(extension).forEach(function (key) { setProp(proto, key, extension[key]); }); } var defineProperty = Object.defineProperty; function setProp(obj, prop, functionOrGetSet, options) { defineProperty(obj, prop, extend(functionOrGetSet && hasOwn(functionOrGetSet, "get") && typeof functionOrGetSet.get === 'function' ? { get: functionOrGetSet.get, set: functionOrGetSet.set, configurable: true } : { value: functionOrGetSet, configurable: true, writable: true }, options)); } function derive(Child) { return { from: function (Parent) { Child.prototype = Object.create(Parent.prototype); setProp(Child.prototype, "constructor", Child); return { extend: props.bind(null, Child.prototype) }; } }; } var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; function getPropertyDescriptor(obj, prop) { var pd = getOwnPropertyDescriptor(obj, prop), proto; return pd || (proto = getProto(obj)) && getPropertyDescriptor(proto, prop); } var _slice = [].slice; function slice(args, start, end) { return _slice.call(args, start, end); } function override(origFunc, overridedFactory) { return overridedFactory(origFunc); } function assert(b) { if (!b) throw new Error("Assertion Failed"); } function asap(fn) { if (_global.setImmediate) setImmediate(fn); else setTimeout(fn, 0); } /** Generate an object (hash map) based on given array. * @param extractor Function taking an array item and its index and returning an array of 2 items ([key, value]) to * instert on the resulting object for each item in the array. If this function returns a falsy value, the * current item wont affect the resulting object. */ function arrayToObject(array, extractor) { return array.reduce(function (result, item, i) { var nameAndValue = extractor(item, i); if (nameAndValue) result[nameAndValue[0]] = nameAndValue[1]; return result; }, {}); } function trycatcher(fn, reject) { return function () { try { fn.apply(this, arguments); } catch (e) { reject(e); } }; } function tryCatch(fn, onerror, args) { try { fn.apply(null, args); } catch (ex) { onerror && onerror(ex); } } function getByKeyPath(obj, keyPath) { // http://www.w3.org/TR/IndexedDB/#steps-for-extracting-a-key-from-a-value-using-a-key-path if (hasOwn(obj, keyPath)) return obj[keyPath]; // This line is moved from last to first for optimization purpose. if (!keyPath) return obj; if (typeof keyPath !== 'string') { var rv = []; for (var i = 0, l = keyPath.length; i < l; ++i) { var val = getByKeyPath(obj, keyPath[i]); rv.push(val); } return rv; } var period = keyPath.indexOf('.'); if (period !== -1) { var innerObj = obj[keyPath.substr(0, period)]; return innerObj === undefined ? undefined : getByKeyPath(innerObj, keyPath.substr(period + 1)); } return undefined; } function setByKeyPath(obj, keyPath, value) { if (!obj || keyPath === undefined) return; if ('isFrozen' in Object && Object.isFrozen(obj)) return; if (typeof keyPath !== 'string' && 'length' in keyPath) { assert(typeof value !== 'string' && 'length' in value); for (var i = 0, l = keyPath.length; i < l; ++i) { setByKeyPath(obj, keyPath[i], value[i]); } } else { var period = keyPath.indexOf('.'); if (period !== -1) { var currentKeyPath = keyPath.substr(0, period); var remainingKeyPath = keyPath.substr(period + 1); if (remainingKeyPath === "") if (value === undefined) delete obj[currentKeyPath]; else obj[currentKeyPath] = value; else { var innerObj = obj[currentKeyPath]; if (!innerObj) innerObj = (obj[currentKeyPath] = {}); setByKeyPath(innerObj, remainingKeyPath, value); } } else { if (value === undefined) delete obj[keyPath]; else obj[keyPath] = value; } } } function delByKeyPath(obj, keyPath) { if (typeof keyPath === 'string') setByKeyPath(obj, keyPath, undefined); else if ('length' in keyPath) [].map.call(keyPath, function (kp) { setByKeyPath(obj, kp, undefined); }); } function shallowClone(obj) { var rv = {}; for (var m in obj) { if (hasOwn(obj, m)) rv[m] = obj[m]; } return rv; } var concat = [].concat; function flatten(a) { return concat.apply([], a); } //https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm var intrinsicTypes = "Boolean,String,Date,RegExp,Blob,File,FileList,ArrayBuffer,DataView,Uint8ClampedArray,ImageData,Map,Set" .split(',').concat(flatten([8, 16, 32, 64].map(function (num) { return ["Int", "Uint", "Float"].map(function (t) { return t + num + "Array"; }); }))).filter(function (t) { return _global[t]; }).map(function (t) { return _global[t]; }); function deepClone(any) { if (!any || typeof any !== 'object') return any; var rv; if (isArray(any)) { rv = []; for (var i = 0, l = any.length; i < l; ++i) { rv.push(deepClone(any[i])); } } else if (intrinsicTypes.indexOf(any.constructor) >= 0) { rv = any; } else { rv = any.constructor ? Object.create(any.constructor.prototype) : {}; for (var prop in any) { if (hasOwn(any, prop)) { rv[prop] = deepClone(any[prop]); } } } return rv; } function getObjectDiff(a, b, rv, prfx) { // Compares objects a and b and produces a diff object. rv = rv || {}; prfx = prfx || ''; keys(a).forEach(function (prop) { if (!hasOwn(b, prop)) rv[prfx + prop] = undefined; // Property removed else { var ap = a[prop], bp = b[prop]; if (typeof ap === 'object' && typeof bp === 'object' && ap && bp && // Now compare constructors are same (not equal because wont work in Safari) ('' + ap.constructor) === ('' + bp.constructor)) // Same type of object but its properties may have changed getObjectDiff(ap, bp, rv, prfx + prop + "."); else if (ap !== bp) rv[prfx + prop] = b[prop]; // Primitive value changed } }); keys(b).forEach(function (prop) { if (!hasOwn(a, prop)) { rv[prfx + prop] = b[prop]; // Property added } }); return rv; } // If first argument is iterable or array-like, return it as an array var iteratorSymbol = typeof Symbol !== 'undefined' && Symbol.iterator; var getIteratorOf = iteratorSymbol ? function (x) { var i; return x != null && (i = x[iteratorSymbol]) && i.apply(x); } : function () { return null; }; var NO_CHAR_ARRAY = {}; // Takes one or several arguments and returns an array based on the following criteras: // * If several arguments provided, return arguments converted to an array in a way that // still allows javascript engine to optimize the code. // * If single argument is an array, return a clone of it. // * If this-pointer equals NO_CHAR_ARRAY, don't accept strings as valid iterables as a special // case to the two bullets below. // * If single argument is an iterable, convert it to an array and return the resulting array. // * If single argument is array-like (has length of type number), convert it to an array. function getArrayOf(arrayLike) { var i, a, x, it; if (arguments.length === 1) { if (isArray(arrayLike)) return arrayLike.slice(); if (this === NO_CHAR_ARRAY && typeof arrayLike === 'string') return [arrayLike]; if ((it = getIteratorOf(arrayLike))) { a = []; while ((x = it.next()), !x.done) a.push(x.value); return a; } if (arrayLike == null) return [arrayLike]; i = arrayLike.length; if (typeof i === 'number') { a = new Array(i); while (i--) a[i] = arrayLike[i]; return a; } return [arrayLike]; } i = arguments.length; a = new Array(i); while (i--) a[i] = arguments[i]; return a; } // By default, debug will be true only if platform is a web platform and its page is served from localhost. // When debug = true, error's stacks will contain asyncronic long stacks. var debug = typeof location !== 'undefined' && // By default, use debug mode if served from localhost. /^(http|https):\/\/(localhost|127\.0\.0\.1)/.test(location.href); function setDebug(value, filter) { debug = value; libraryFilter = filter; } var libraryFilter = function () { return true; }; var NEEDS_THROW_FOR_STACK = !new Error("").stack; function getErrorWithStack() { "use strict"; if (NEEDS_THROW_FOR_STACK) try { // Doing something naughty in strict mode here to trigger a specific error // that can be explicitely ignored in debugger's exception settings. // If we'd just throw new Error() here, IE's debugger's exception settings // will just consider it as "exception thrown by javascript code" which is // something you wouldn't want it to ignore. getErrorWithStack.arguments; throw new Error(); // Fallback if above line don't throw. } catch (e) { return e; } return new Error(); } function prettyStack(exception, numIgnoredFrames) { var stack = exception.stack; if (!stack) return ""; numIgnoredFrames = (numIgnoredFrames || 0); if (stack.indexOf(exception.name) === 0) numIgnoredFrames += (exception.name + exception.message).split('\n').length; return stack.split('\n') .slice(numIgnoredFrames) .filter(libraryFilter) .map(function (frame) { return "\n" + frame; }) .join(''); } function deprecated(what, fn) { return function () { console.warn(what + " is deprecated. See https://github.com/dfahlander/Dexie.js/wiki/Deprecations. " + prettyStack(getErrorWithStack(), 1)); return fn.apply(this, arguments); }; } var dexieErrorNames = [ 'Modify', 'Bulk', 'OpenFailed', 'VersionChange', 'Schema', 'Upgrade', 'InvalidTable', 'MissingAPI', 'NoSuchDatabase', 'InvalidArgument', 'SubTransaction', 'Unsupported', 'Internal', 'DatabaseClosed', 'PrematureCommit', 'ForeignAwait' ]; var idbDomErrorNames = [ 'Unknown', 'Constraint', 'Data', 'TransactionInactive', 'ReadOnly', 'Version', 'NotFound', 'InvalidState', 'InvalidAccess', 'Abort', 'Timeout', 'QuotaExceeded', 'Syntax', 'DataClone' ]; var errorList = dexieErrorNames.concat(idbDomErrorNames); var defaultTexts = { VersionChanged: "Database version changed by other database connection", DatabaseClosed: "Database has been closed", Abort: "Transaction aborted", TransactionInactive: "Transaction has already completed or failed" }; // // DexieError - base class of all out exceptions. // function DexieError(name, msg) { // Reason we don't use ES6 classes is because: // 1. It bloats transpiled code and increases size of minified code. // 2. It doesn't give us much in this case. // 3. It would require sub classes to call super(), which // is not needed when deriving from Error. this._e = getErrorWithStack(); this.name = name; this.message = msg; } derive(DexieError).from(Error).extend({ stack: { get: function () { return this._stack || (this._stack = this.name + ": " + this.message + prettyStack(this._e, 2)); } }, toString: function () { return this.name + ": " + this.message; } }); function getMultiErrorMessage(msg, failures) { return msg + ". Errors: " + failures .map(function (f) { return f.toString(); }) .filter(function (v, i, s) { return s.indexOf(v) === i; }) // Only unique error strings .join('\n'); } // // ModifyError - thrown in Collection.modify() // Specific constructor because it contains members failures and failedKeys. // function ModifyError(msg, failures, successCount, failedKeys) { this._e = getErrorWithStack(); this.failures = failures; this.failedKeys = failedKeys; this.successCount = successCount; } derive(ModifyError).from(DexieError); function BulkError(msg, failures) { this._e = getErrorWithStack(); this.name = "BulkError"; this.failures = failures; this.message = getMultiErrorMessage(msg, failures); } derive(BulkError).from(DexieError); // // // Dynamically generate error names and exception classes based // on the names in errorList. // // // Map of {ErrorName -> ErrorName + "Error"} var errnames = errorList.reduce(function (obj, name) { return (obj[name] = name + "Error", obj); }, {}); // Need an alias for DexieError because we're gonna create subclasses with the same name. var BaseException = DexieError; // Map of {ErrorName -> exception constructor} var exceptions = errorList.reduce(function (obj, name) { // Let the name be "DexieError" because this name may // be shown in call stack and when debugging. DexieError is // the most true name because it derives from DexieError, // and we cannot change Function.name programatically without // dynamically create a Function object, which would be considered // 'eval-evil'. var fullName = name + "Error"; function DexieError(msgOrInner, inner) { this._e = getErrorWithStack(); this.name = fullName; if (!msgOrInner) { this.message = defaultTexts[name] || fullName; this.inner = null; } else if (typeof msgOrInner === 'string') { this.message = msgOrInner; this.inner = inner || null; } else if (typeof msgOrInner === 'object') { this.message = msgOrInner.name + " " + msgOrInner.message; this.inner = msgOrInner; } } derive(DexieError).from(BaseException); obj[name] = DexieError; return obj; }, {}); // Use ECMASCRIPT standard exceptions where applicable: exceptions.Syntax = SyntaxError; exceptions.Type = TypeError; exceptions.Range = RangeError; var exceptionMap = idbDomErrorNames.reduce(function (obj, name) { obj[name + "Error"] = exceptions[name]; return obj; }, {}); function mapError(domError, message) { if (!domError || domError instanceof DexieError || domError instanceof TypeError || domError instanceof SyntaxError || !domError.name || !exceptionMap[domError.name]) return domError; var rv = new exceptionMap[domError.name](message || domError.message, domError); if ("stack" in domError) { // Derive stack from inner exception if it has a stack setProp(rv, "stack", { get: function () { return this.inner.stack; } }); } return rv; } var fullNameExceptions = errorList.reduce(function (obj, name) { if (["Syntax", "Type", "Range"].indexOf(name) === -1) obj[name + "Error"] = exceptions[name]; return obj; }, {}); fullNameExceptions.ModifyError = ModifyError; fullNameExceptions.DexieError = DexieError; fullNameExceptions.BulkError = BulkError; function nop() { } function mirror(val) { return val; } function pureFunctionChain(f1, f2) { // Enables chained events that takes ONE argument and returns it to the next function in chain. // This pattern is used in the hook("reading") event. if (f1 == null || f1 === mirror) return f2; return function (val) { return f2(f1(val)); }; } function callBoth(on1, on2) { return function () { on1.apply(this, arguments); on2.apply(this, arguments); }; } function hookCreatingChain(f1, f2) { // Enables chained events that takes several arguments and may modify first argument by making a modification and then returning the same instance. // This pattern is used in the hook("creating") event. if (f1 === nop) return f2; return function () { var res = f1.apply(this, arguments); if (res !== undefined) arguments[0] = res; var onsuccess = this.onsuccess, // In case event listener has set this.onsuccess onerror = this.onerror; // In case event listener has set this.onerror this.onsuccess = null; this.onerror = null; var res2 = f2.apply(this, arguments); if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; return res2 !== undefined ? res2 : res; }; } function hookDeletingChain(f1, f2) { if (f1 === nop) return f2; return function () { f1.apply(this, arguments); var onsuccess = this.onsuccess, // In case event listener has set this.onsuccess onerror = this.onerror; // In case event listener has set this.onerror this.onsuccess = this.onerror = null; f2.apply(this, arguments); if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; }; } function hookUpdatingChain(f1, f2) { if (f1 === nop) return f2; return function (modifications) { var res = f1.apply(this, arguments); extend(modifications, res); // If f1 returns new modifications, extend caller's modifications with the result before calling next in chain. var onsuccess = this.onsuccess, // In case event listener has set this.onsuccess onerror = this.onerror; // In case event listener has set this.onerror this.onsuccess = null; this.onerror = null; var res2 = f2.apply(this, arguments); if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; return res === undefined ? (res2 === undefined ? undefined : res2) : (extend(res, res2)); }; } function reverseStoppableEventChain(f1, f2) { if (f1 === nop) return f2; return function () { if (f2.apply(this, arguments) === false) return false; return f1.apply(this, arguments); }; } function promisableChain(f1, f2) { if (f1 === nop) return f2; return function () { var res = f1.apply(this, arguments); if (res && typeof res.then === 'function') { var thiz = this, i = arguments.length, args = new Array(i); while (i--) args[i] = arguments[i]; return res.then(function () { return f2.apply(thiz, args); }); } return f2.apply(this, arguments); }; } /* * Copyright (c) 2014-2017 David Fahlander * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/LICENSE-2.0 */ // // Promise and Zone (PSD) for Dexie library // // I started out writing this Promise class by copying promise-light (https://github.com/taylorhakes/promise-light) by // https://github.com/taylorhakes - an A+ and ECMASCRIPT 6 compliant Promise implementation. // // In previous versions this was fixed by not calling setTimeout when knowing that the resolve() or reject() came from another // tick. In Dexie v1.4.0, I've rewritten the Promise class entirely. Just some fragments of promise-light is left. I use // another strategy now that simplifies everything a lot: to always execute callbacks in a new micro-task, but have an own micro-task // engine that is indexedDB compliant across all browsers. // Promise class has also been optimized a lot with inspiration from bluebird - to avoid closures as much as possible. // Also with inspiration from bluebird, asyncronic stacks in debug mode. // // Specific non-standard features of this Promise class: // * Custom zone support (a.k.a. PSD) with ability to keep zones also when using native promises as well as // native async / await. // * Promise.follow() method built upon the custom zone engine, that allows user to track all promises created from current stack frame // and below + all promises that those promises creates or awaits. // * Detect any unhandled promise in a PSD-scope (PSD.onunhandled). // // David Fahlander, https://github.com/dfahlander // // Just a pointer that only this module knows about. // Used in Promise constructor to emulate a private constructor. var INTERNAL = {}; // Async stacks (long stacks) must not grow infinitely. var LONG_STACKS_CLIP_LIMIT = 100; var MAX_LONG_STACKS = 20; var ZONE_ECHO_LIMIT = 7; var nativePromiseInstanceAndProto = (function () { try { // Be able to patch native async functions return new Function("let F=async ()=>{},p=F();return [p,Object.getPrototypeOf(p),Promise.resolve(),F.constructor];")(); } catch (e) { var P = _global.Promise; return P ? [P.resolve(), P.prototype, P.resolve()] : []; } })(); var resolvedNativePromise = nativePromiseInstanceAndProto[0]; var nativePromiseProto = nativePromiseInstanceAndProto[1]; var resolvedGlobalPromise = nativePromiseInstanceAndProto[2]; var nativePromiseThen = nativePromiseProto && nativePromiseProto.then; var NativePromise = resolvedNativePromise && resolvedNativePromise.constructor; var AsyncFunction = nativePromiseInstanceAndProto[3]; var patchGlobalPromise = !!resolvedGlobalPromise; var stack_being_generated = false; /* The default function used only for the very first promise in a promise chain. As soon as then promise is resolved or rejected, all next tasks will be executed in micro ticks emulated in this module. For indexedDB compatibility, this means that every method needs to execute at least one promise before doing an indexedDB operation. Dexie will always call db.ready().then() for every operation to make sure the indexedDB event is started in an indexedDB-compatible emulated micro task loop. */ var schedulePhysicalTick = resolvedGlobalPromise ? function () { resolvedGlobalPromise.then(physicalTick); } : _global.setImmediate ? // setImmediate supported. Those modern platforms also supports Function.bind(). setImmediate.bind(null, physicalTick) : _global.MutationObserver ? // MutationObserver supported function () { var hiddenDiv = document.createElement("div"); (new MutationObserver(function () { physicalTick(); hiddenDiv = null; })).observe(hiddenDiv, { attributes: true }); hiddenDiv.setAttribute('i', '1'); } : // No support for setImmediate or MutationObserver. No worry, setTimeout is only called // once time. Every tick that follows will be our emulated micro tick. // Could have uses setTimeout.bind(null, 0, physicalTick) if it wasnt for that FF13 and below has a bug function () { setTimeout(physicalTick, 0); }; // Configurable through Promise.scheduler. // Don't export because it would be unsafe to let unknown // code call it unless they do try..catch within their callback. // This function can be retrieved through getter of Promise.scheduler though, // but users must not do Promise.scheduler = myFuncThatThrowsException var asap$1 = function (callback, args) { microtickQueue.push([callback, args]); if (needsNewPhysicalTick) { schedulePhysicalTick(); needsNewPhysicalTick = false; } }; var isOutsideMicroTick = true; var needsNewPhysicalTick = true; var unhandledErrors = []; var rejectingErrors = []; var currentFulfiller = null; var rejectionMapper = mirror; // Remove in next major when removing error mapping of DOMErrors and DOMExceptions var globalPSD = { id: 'global', global: true, ref: 0, unhandleds: [], onunhandled: globalError, pgp: false, env: {}, finalize: function () { this.unhandleds.forEach(function (uh) { try { globalError(uh[0], uh[1]); } catch (e) { } }); } }; var PSD = globalPSD; var microtickQueue = []; // Callbacks to call in this or next physical tick. var numScheduledCalls = 0; // Number of listener-calls left to do in this physical tick. var tickFinalizers = []; // Finalizers to call when there are no more async calls scheduled within current physical tick. function Promise(fn) { if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); this._listeners = []; this.onuncatched = nop; // Deprecate in next major. Not needed. Better to use global error handler. // A library may set `promise._lib = true;` after promise is created to make resolve() or reject() // execute the microtask engine implicitely within the call to resolve() or reject(). // To remain A+ compliant, a library must only set `_lib=true` if it can guarantee that the stack // only contains library code when calling resolve() or reject(). // RULE OF THUMB: ONLY set _lib = true for promises explicitely resolving/rejecting directly from // global scope (event handler, timer etc)! this._lib = false; // Current async scope var psd = (this._PSD = PSD); if (debug) { this._stackHolder = getErrorWithStack(); this._prev = null; this._numPrev = 0; // Number of previous promises (for long stacks) } if (typeof fn !== 'function') { if (fn !== INTERNAL) throw new TypeError('Not a function'); // Private constructor (INTERNAL, state, value). // Used internally by Promise.resolve() and Promise.reject(). this._state = arguments[1]; this._value = arguments[2]; if (this._state === false) handleRejection(this, this._value); // Map error, set stack and addPossiblyUnhandledError(). return; } this._state = null; // null (=pending), false (=rejected) or true (=resolved) this._value = null; // error or result ++psd.ref; // Refcounting current scope executePromiseTask(this, fn); } // Prepare a property descriptor to put onto Promise.prototype.then var thenProp = { get: function () { var psd = PSD, microTaskId = totalEchoes; function then(onFulfilled, onRejected) { var _this = this; var possibleAwait = !psd.global && (psd !== PSD || microTaskId !== totalEchoes); if (possibleAwait) decrementExpectedAwaits(); var rv = new Promise(function (resolve, reject) { propagateToListener(_this, new Listener(nativeAwaitCompatibleWrap(onFulfilled, psd, possibleAwait), nativeAwaitCompatibleWrap(onRejected, psd, possibleAwait), resolve, reject, psd)); }); debug && linkToPreviousPromise(rv, this); return rv; } then.prototype = INTERNAL; // For idempotense, see setter below. return then; }, // Be idempotent and allow another framework (such as zone.js or another instance of a Dexie.Promise module) to replace Promise.prototype.then // and when that framework wants to restore the original property, we must identify that and restore the original property descriptor. set: function (value) { setProp(this, 'then', value && value.prototype === INTERNAL ? thenProp : // Restore to original property descriptor. { get: function () { return value; // Getter returning provided value (behaves like value is just changed) }, set: thenProp.set // Keep a setter that is prepared to restore original. }); } }; props(Promise.prototype, { then: thenProp, _then: function (onFulfilled, onRejected) { // A little tinier version of then() that don't have to create a resulting promise. propagateToListener(this, new Listener(null, null, onFulfilled, onRejected, PSD)); }, catch: function (onRejected) { if (arguments.length === 1) return this.then(null, onRejected); // First argument is the Error type to catch var type = arguments[0], handler = arguments[1]; return typeof type === 'function' ? this.then(null, function (err) { // Catching errors by its constructor type (similar to java / c++ / c#) // Sample: promise.catch(TypeError, function (e) { ... }); return err instanceof type ? handler(err) : PromiseReject(err); }) : this.then(null, function (err) { // Catching errors by the error.name property. Makes sense for indexedDB where error type // is always DOMError but where e.name tells the actual error type. // Sample: promise.catch('ConstraintError', function (e) { ... }); return err && err.name === type ? handler(err) : PromiseReject(err); }); }, finally: function (onFinally) { return this.then(function (value) { onFinally(); return value; }, function (err) { onFinally(); return PromiseReject(err); }); }, stack: { get: function () { if (this._stack) return this._stack; try { stack_being_generated = true; var stacks = getStack(this, [], MAX_LONG_STACKS); var stack = stacks.join("\nFrom previous: "); if (this._state !== null) this._stack = stack; // Stack may be updated on reject. return stack; } finally { stack_being_generated = false; } } }, timeout: function (ms, msg) { var _this = this; return ms < Infinity ? new Promise(function (resolve, reject) { var handle = setTimeout(function () { return reject(new exceptions.Timeout(msg)); }, ms); _this.then(resolve, reject).finally(clearTimeout.bind(null, handle)); }) : this; } }); if (typeof Symbol !== 'undefined' && Symbol.toStringTag) setProp(Promise.prototype, Symbol.toStringTag, 'Promise'); // Now that Promise.prototype is defined, we have all it takes to set globalPSD.env. // Environment globals snapshotted on leaving global zone globalPSD.env = snapShot(); function Listener(onFulfilled, onRejected, resolve, reject, zone) { this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; this.onRejected = typeof onRejected === 'function' ? onRejected : null; this.resolve = resolve; this.reject = reject; this.psd = zone; } // Promise Static Properties props(Promise, { all: function () { var values = getArrayOf.apply(null, arguments) // Supports iterables, implicit arguments and array-like. .map(onPossibleParallellAsync); // Handle parallell async/awaits return new Promise(function (resolve, reject) { if (values.length === 0) resolve([]); var remaining = values.length; values.forEach(function (a, i) { return Promise.resolve(a).then(function (x) { values[i] = x; if (!--remaining) resolve(values); }, reject); }); }); }, resolve: function (value) { if (value instanceof Promise) return value; if (value && typeof value.then === 'function') return new Promise(function (resolve, reject) { value.then(resolve, reject); }); var rv = new Promise(INTERNAL, true, value); linkToPreviousPromise(rv, currentFulfiller); return rv; }, reject: PromiseReject, race: function () { var values = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); return new Promise(function (resolve, reject) { values.map(function (value) { return Promise.resolve(value).then(resolve, reject); }); }); }, PSD: { get: function () { return PSD; }, set: function (value) { return PSD = value; } }, //totalEchoes: {get: ()=>totalEchoes}, //task: {get: ()=>task}, newPSD: newScope, usePSD: usePSD, scheduler: { get: function () { return asap$1; }, set: function (value) { asap$1 = value; } }, rejectionMapper: { get: function () { return rejectionMapper; }, set: function (value) { rejectionMapper = value; } // Map reject failures }, follow: function (fn, zoneProps) { return new Promise(function (resolve, reject) { return newScope(function (resolve, reject) { var psd = PSD; psd.unhandleds = []; // For unhandled standard- or 3rd party Promises. Checked at psd.finalize() psd.onunhandled = reject; // Triggered directly on unhandled promises of this library. psd.finalize = callBoth(function () { var _this = this; // Unhandled standard or 3rd part promises are put in PSD.unhandleds and // examined upon scope completion while unhandled rejections in this Promise // will trigger directly through psd.onunhandled run_at_end_of_this_or_next_physical_tick(function () { _this.unhandleds.length === 0 ? resolve() : reject(_this.unhandleds[0]); }); }, psd.finalize); fn(); }, zoneProps, resolve, reject); }); } }); /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. */ function executePromiseTask(promise, fn) { // Promise Resolution Procedure: // https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure try { fn(function (value) { if (promise._state !== null) return; // Already settled if (value === promise) throw new TypeError('A promise cannot be resolved with itself.'); var shouldExecuteTick = promise._lib && beginMicroTickScope(); if (value && typeof value.then === 'function') { executePromiseTask(promise, function (resolve, reject) { value instanceof Promise ? value._then(resolve, reject) : value.then(resolve, reject); }); } else { promise._state = true; promise._value = value; propagateAllListeners(promise); } if (shouldExecuteTick) endMicroTickScope(); }, handleRejection.bind(null, promise)); // If Function.bind is not supported. Exception is handled in catch below } catch (ex) { handleRejection(promise, ex); } } function handleRejection(promise, reason) { rejectingErrors.push(reason); if (promise._state !== null) return; var shouldExecuteTick = promise._lib && beginMicroTickScope(); reason = rejectionMapper(reason); promise._state = false; promise._value = reason; debug && reason !== null && typeof reason === 'object' && !reason._promise && tryCatch(function () { var origProp = getPropertyDescriptor(reason, "stack"); reason._promise = promise; setProp(reason, "stack", { get: function () { return stack_being_generated ? origProp && (origProp.get ? origProp.get.apply(reason) : origProp.value) : promise.stack; } }); }); // Add the failure to a list of possibly uncaught errors addPossiblyUnhandledError(promise); propagateAllListeners(promise); if (shouldExecuteTick) endMicroTickScope(); } function propagateAllListeners(promise) { //debug && linkToPreviousPromise(promise); var listeners = promise._listeners; promise._listeners = []; for (var i = 0, len = listeners.length; i < len; ++i) { propagateToListener(promise, listeners[i]); } var psd = promise._PSD; --psd.ref || psd.finalize(); // if psd.ref reaches zero, call psd.finalize(); if (numScheduledCalls === 0) { // If numScheduledCalls is 0, it means that our stack is not in a callback of a scheduled call, // and that no deferreds where listening to this rejection or success. // Since there is a risk that our stack can contain application code that may // do stuff after this code is finished that may generate new calls, we cannot // call finalizers here. ++numScheduledCalls; asap$1(function () { if (--numScheduledCalls === 0) finalizePhysicalTick(); // Will detect unhandled errors }, []); } } function propagateToListener(promise, listener) { if (promise._state === null) { promise._listeners.push(listener); return; } var cb = promise._state ? listener.onFulfilled : listener.onRejected; if (cb === null) { // This Listener doesnt have a listener for the event being triggered (onFulfilled or onReject) so lets forward the event to any eventual listeners on the Promise instance returned by then() or catch() return (promise._state ? listener.resolve : listener.reject)(promise._value); } ++listener.psd.ref; ++numScheduledCalls; asap$1(callListener, [cb, promise, listener]); } function callListener(cb, promise, listener) { try { // Set static variable currentFulfiller to the promise that is being fullfilled, // so that we connect the chain of promises (for long stacks support) currentFulfiller = promise; // Call callback and resolve our listener with it's return value. var ret, value = promise._value; if (promise._state) { // cb is onResolved ret = cb(value); } else { // cb is onRejected if (rejectingErrors.length) rejectingErrors = []; ret = cb(value); if (rejectingErrors.indexOf(value) === -1) markErrorAsHandled(promise); // Callback didnt do Promise.reject(err) nor reject(err) onto another promise. } listener.resolve(ret); } catch (e) { // Exception thrown in callback. Reject our listener. listener.reject(e); } finally { // Restore env and currentFulfiller. currentFulfiller = null; if (--numScheduledCalls === 0) finalizePhysicalTick(); --listener.psd.ref || listener.psd.finalize(); } } function getStack(promise, stacks, limit) { if (stacks.length === limit) return stacks; var stack = ""; if (promise._state === false) { var failure = promise._value, errorName, message; if (failure != null) { errorName = failure.name || "Error"; message = failure.message || failure; stack = prettyStack(failure, 0); } else { errorName = failure; // If error is undefined or null, show that. message = ""; } stacks.push(errorName + (message ? ": " + message : "") + stack); } if (debug) { stack = prettyStack(promise._stackHolder, 2); if (stack && stacks.indexOf(stack) === -1) stacks.push(stack); if (promise._prev) getStack(promise._prev, stacks, limit); } return stacks; } function linkToPreviousPromise(promise, prev) { // Support long stacks by linking to previous completed promise. var numPrev = prev ? prev._numPrev + 1 : 0; if (numPrev < LONG_STACKS_CLIP_LIMIT) { promise._prev = prev; promise._numPrev = numPrev; } } /* The callback to schedule with setImmediate() or setTimeout(). It runs a virtual microtick and executes any callback registered in microtickQueue. */ function physicalTick() { beginMicroTickScope() && endMicroTickScope(); } function beginMicroTickScope() { var wasRootExec = isOutsideMicroTick; isOutsideMicroTick = false; needsNewPhysicalTick = false; return wasRootExec; } /* Executes micro-ticks without doing try..catch. This can be possible because we only use this internally and the registered functions are exception-safe (they do try..catch internally before calling any external method). If registering functions in the microtickQueue that are not exception-safe, this would destroy the framework and make it instable. So we don't export our asap method. */ function endMicroTickScope() { var callbacks, i, l; do { while (microtickQueue.length > 0) { callbacks = microtickQueue; microtickQueue = []; l = callbacks.length; for (i = 0; i < l; ++i) { var item = callbacks[i]; item[0].apply(null, item[1]); } } } while (microtickQueue.length > 0); isOutsideMicroTick = true; needsNewPhysicalTick = true; } function finalizePhysicalTick() { var unhandledErrs = unhandledErrors; unhandledErrors = []; unhandledErrs.forEach(function (p) { p._PSD.onunhandled.call(null, p._value, p); }); var finalizers = tickFinalizers.slice(0); // Clone first because finalizer may remove itself from list. var i = finalizers.length; while (i) finalizers[--i](); } function run_at_end_of_this_or_next_physical_tick(fn) { function finalizer() { fn(); tickFinalizers.splice(tickFinalizers.indexOf(finalizer), 1); } tickFinalizers.push(finalizer); ++numScheduledCalls; asap$1(function () { if (--numScheduledCalls === 0) finalizePhysicalTick(); }, []); } function addPossiblyUnhandledError(promise) { // Only add to unhandledErrors if not already there. The first one to add to this list // will be upon the first rejection so that the root cause (first promise in the // rejection chain) is the one listed. if (!unhandledErrors.some(function (p) { return p._value === promise._value; })) unhandledErrors.push(promise); } function markErrorAsHandled(promise) { // Called when a reject handled is actually being called. // Search in unhandledErrors for any promise whos _value is this promise_value (list // contains only rejected promises, and only one item per error) var i = unhandledErrors.length; while (i) if (unhandledErrors[--i]._value === promise._value) { // Found a promise that failed with this same error object pointer, // Remove that since there is a listener that actually takes care of it. unhandledErrors.splice(i, 1); return; } } function PromiseReject(reason) { return new Promise(INTERNAL, false, reason); } function wrap(fn, errorCatcher) { var psd = PSD; return function () { var wasRootExec = beginMicroTickScope(), outerScope = PSD; try { switchToZone(psd, true); return fn.apply(this, arguments); } catch (e) { errorCatcher && errorCatcher(e); } finally { switchToZone(outerScope, false); if (wasRootExec) endMicroTickScope(); } }; } // // variables used for native await support // var task = { awaits: 0, echoes: 0, id: 0 }; // The ongoing macro-task when using zone-echoing. var taskCounter = 0; // ID counter for macro tasks. var zoneStack = []; // Stack of left zones to restore asynchronically. var zoneEchoes = 0; // zoneEchoes is a must in order to persist zones between native await expressions. var totalEchoes = 0; // ID counter for micro-tasks. Used to detect possible native await in our Promise.prototype.then. var zone_id_counter = 0; function newScope(fn, props$$1, a1, a2) { var parent = PSD, psd = Object.create(parent); psd.parent = parent; psd.ref = 0; psd.global = false; psd.id = ++zone_id_counter; // Prepare for promise patching (done in usePSD): var globalEnv = globalPSD.env; psd.env = patchGlobalPromise ? { Promise: Promise, PromiseProp: { value: Promise, configurable: true, writable: true }, all: Promise.all, race: Promise.race, resolve: Promise.resolve, reject: Promise.reject, nthen: getPatchedPromiseThen(globalEnv.nthen, psd), gthen: getPatchedPromiseThen(globalEnv.gthen, psd) // global then } : {}; if (props$$1) extend(psd, props$$1); // unhandleds and onunhandled should not be specifically set here. // Leave them on parent prototype. // unhandleds.push(err) will push to parent's prototype // onunhandled() will call parents onunhandled (with this scope's this-pointer though!) ++parent.ref; psd.finalize = function () { --this.parent.ref || this.parent.finalize(); }; var rv = usePSD(psd, fn, a1, a2); if (psd.ref === 0) psd.finalize(); return rv; } // Function to call if scopeFunc returns NativePromise // Also for each NativePromise in the arguments to Promise.all() function incrementExpectedAwaits() { if (!task.id) task.id = ++taskCounter; ++task.awaits; task.echoes += ZONE_ECHO_LIMIT; return task.id; } // Function to call when 'then' calls back on a native promise where onAwaitExpected() had been called. // Also call this when a native await calls then method on a promise. In that case, don't supply // sourceTaskId because we already know it refers to current task. function decrementExpectedAwaits(sourceTaskId) { if (!task.awaits || (sourceTaskId && sourceTaskId !== task.id)) return; if (--task.awaits === 0) task.id = 0; task.echoes = task.awaits * ZONE_ECHO_LIMIT; // Will reset echoes to 0 if awaits is 0. } // Call from Promise.all() and Promise.race() function onPossibleParallellAsync(possiblePromise) { if (task.echoes && possiblePromise && possiblePromise.constructor === NativePromise) { incrementExpectedAwaits(); return possiblePromise.then(function (x) { decrementExpectedAwaits(); return x; }, function (e) { decrementExpectedAwaits(); return rejection(e); }); } return possiblePromise; } function zoneEnterEcho(targetZone) { ++totalEchoes; if (!task.echoes || --task.echoes === 0) { task.echoes = task.id = 0; // Cancel zone echoing. } zoneStack.push(PSD); switchToZone(targetZone, true); } function zoneLeaveEcho() { var zone = zoneStack[zoneStack.length - 1]; zoneStack.pop(); switchToZone(zone, false); } function switchToZone(targetZone, bEnteringZone) { var currentZone = PSD; if (bEnteringZone ? task.echoes && (!zoneEchoes++ || targetZone !== PSD) : zoneEchoes && (!--zoneEchoes || targetZone !== PSD)) { // Enter or leave zone asynchronically as well, so that tasks initiated during current tick // will be surrounded by the zone when they are invoked. enqueueNativeMicroTask(bEnteringZone ? zoneEnterEcho.bind(null, targetZone) : zoneLeaveEcho); } if (targetZone === PSD) return; PSD = targetZone; // The actual zone switch occurs at this line. // Snapshot on every leave from global zone. if (currentZone === globalPSD) globalPSD.env = snapShot(); if