UNPKG

modern-async

Version:

A modern tooling library for asynchronous operations using async/await, promises and async generators

3,091 lines • 114 kB
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.modernAsync = {}));
})(this, (function (exports) { 'use strict';

  /**
   * Wraps an iterable or async iterable into an iterable that is guaranted to be async.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
   * @yields {any} The elements returned by the original iterable.
   * @example
   * import { asyncIterableWrap } from 'modern-async'
   *
   * // example sync generator
   * function* syncGenerator() {
   *   for (let i = 0; i < 3; i += 1) {
   *     yield i
   *   }
   * }
   *
   * const asyncIterable = asyncIterableWrap(syncGenerator())
   *
   * for await (const el of asyncIterable) {
   *   console.log(el)
   * }
   * // will print:
   * // 0
   * // 1
   * // 2
   */
  async function * asyncIterableWrap (iterable) {
    for await (const el of iterable) {
      yield el;
    }
  }

  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

  var nanoassert = assert;

  class AssertionError extends Error {}
  AssertionError.prototype.name = 'AssertionError';

  /**
   * Minimal assert function
   * @param  {any} t Value to check if falsy
   * @param  {string=} m Optional assertion error message
   * @throws {AssertionError}
   */
  function assert (t, m) {
    if (!t) {
      var err = new AssertionError(m);
      if (Error.captureStackTrace) Error.captureStackTrace(err, assert);
      throw err
    }
  }

  /**
   * Wraps a function call that may be synchronous in a function that
   * is guaranted to be async. This is a stricter version of calling a
   * function and wrapping its result using `Promise.resolve()` as the new function also
   * handles the case where the original function throws an exception.
   *
   * @param {Function} fct The function to wrap.
   * @returns {Function} The wrapped function.
   * @example
   * import { asyncWrap } from 'modern-async'
   *
   * const myFunction = () => {
   *   // any kind of function that may or may not return a promise
   * }
   *
   * const asyncFct = asyncWrap(myFunction)
   *
   * const promise = asyncFct()
   * console.log(promise instanceof Promise) // prints true
   */
  function asyncWrap (fct) {
    nanoassert(typeof fct === 'function', 'fct must be a function');
    return async function () {
      return fct(...arguments)
    }
  }

  /**
   * Immediately calls an asynchronous function and redirects to an error handler if it throws an exception.
   * The error handler is optional, the default one just outputs the error in the console.
   *
   * This function is trivial but useful when you can't use top-level await for compatibility reasons.
   *
   * @param {Function} fct An asynchronous function to call.
   * @param {Function} [errorHandler] A facultative error handler. This function will receive a single argument:
   * the thrown exception. The default behavior is to output the exception in the console.
   * @example
   * import { asyncRoot } from 'modern-async'
   *
   * asyncRoot(async () => {
   *   // any code using await
   * }, (e) => {
   *   console.error("An error occured", e)
   *   process.exit(-1)
   * })
   */
  async function asyncRoot (fct, errorHandler = null) {
    errorHandler = errorHandler || ((e) => {
      console.error(e);
    });
    const asyncFct = asyncWrap(fct);
    try {
      await asyncFct();
    } catch (e) {
      errorHandler(e);
    }
  }

  /**
   * An error type which is used when a promise is cancelled.
   */
  class CancelledError extends Error {
    /**
     * Constructs a new instance.
     *
     * @param {string} message The error message
     */
    constructor (message) {
      super(message);
      this.name = this.constructor.name;
    }
  }

  /**
   * A basic class to create a promise with its resolve and reject function in the same object.
   *
   * Instances of this class are never returned by any function of this library but it is used
   * internally and can be useful to code other asynchronous helpers.
   *
   * @example
   * import { Deferred, asyncSleep } from 'modern-async'
   *
   * const deferred = new Deferred()
   *
   * asyncSleep(10).then(() => {
   *   deferred.resolve('test')
   * })
   *
   * console.log(await deferred.promise) // will wait 10ms before printing 'test'
   */
  class Deferred {
    /**
     * Constructs a deferred object.
     */
    constructor () {
      this._promise = new Promise((resolve, reject) => {
        this._resolve = resolve;
        this._reject = reject;
      });
    }

    /**
     * (Read-only) The promise.
     *
     * @member {Promise}
     * @returns {Promise} ignored
     */
    get promise () {
      return this._promise
    }

    /**
     * (Read-only) The resolve function.
     *
     * @member {Function}
     * @returns {Function} The resolve function
     */
    get resolve () {
      return this._resolve
    }

    /**
     * (Read-only) The reject function
     *
     * @member {Function}
     * @returns {Function} The reject function
     */
    get reject () {
      return this._reject
    }
  }

  var check = function (it) {
    return it && it.Math == Math && it;
  };

  // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028
  var global$o =
    // eslint-disable-next-line es/no-global-this -- safe
    check(typeof globalThis == 'object' && globalThis) ||
    check(typeof window == 'object' && window) ||
    // eslint-disable-next-line no-restricted-globals -- safe
    check(typeof self == 'object' && self) ||
    check(typeof commonjsGlobal == 'object' && commonjsGlobal) ||
    // eslint-disable-next-line no-new-func -- fallback
    (function () { return this; })() || Function('return this')();

  var FunctionPrototype$1 = Function.prototype;
  var apply$2 = FunctionPrototype$1.apply;
  var bind$5 = FunctionPrototype$1.bind;
  var call$5 = FunctionPrototype$1.call;

  // eslint-disable-next-line es/no-reflect -- safe
  var functionApply = typeof Reflect == 'object' && Reflect.apply || (bind$5 ? call$5.bind(apply$2) : function () {
    return call$5.apply(apply$2, arguments);
  });

  var FunctionPrototype = Function.prototype;
  var bind$4 = FunctionPrototype.bind;
  var call$4 = FunctionPrototype.call;
  var callBind = bind$4 && bind$4.bind(call$4);

  var functionUncurryThis = bind$4 ? function (fn) {
    return fn && callBind(call$4, fn);
  } : function (fn) {
    return fn && function () {
      return call$4.apply(fn, arguments);
    };
  };

  // `IsCallable` abstract operation
  // https://tc39.es/ecma262/#sec-iscallable
  var isCallable$8 = function (argument) {
    return typeof argument == 'function';
  };

  var objectGetOwnPropertyDescriptor = {};

  var fails$6 = function (exec) {
    try {
      return !!exec();
    } catch (error) {
      return true;
    }
  };

  var fails$5 = fails$6;

  // Detect IE8's incomplete defineProperty implementation
  var descriptors = !fails$5(function () {
    // eslint-disable-next-line es/no-object-defineproperty -- required for testing
    return Object.defineProperty({}, 1, { get: function () { return 7; } })[1] != 7;
  });

  var call$3 = Function.prototype.call;

  var functionCall = call$3.bind ? call$3.bind(call$3) : function () {
    return call$3.apply(call$3, arguments);
  };

  var objectPropertyIsEnumerable = {};

  var $propertyIsEnumerable = {}.propertyIsEnumerable;
  // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
  var getOwnPropertyDescriptor$2 = Object.getOwnPropertyDescriptor;

  // Nashorn ~ JDK8 bug
  var NASHORN_BUG = getOwnPropertyDescriptor$2 && !$propertyIsEnumerable.call({ 1: 2 }, 1);

  // `Object.prototype.propertyIsEnumerable` method implementation
  // https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable
  objectPropertyIsEnumerable.f = NASHORN_BUG ? function propertyIsEnumerable(V) {
    var descriptor = getOwnPropertyDescriptor$2(this, V);
    return !!descriptor && descriptor.enumerable;
  } : $propertyIsEnumerable;

  var createPropertyDescriptor$2 = function (bitmap, value) {
    return {
      enumerable: !(bitmap & 1),
      configurable: !(bitmap & 2),
      writable: !(bitmap & 4),
      value: value
    };
  };

  var uncurryThis$7 = functionUncurryThis;

  var toString$1 = uncurryThis$7({}.toString);
  var stringSlice = uncurryThis$7(''.slice);

  var classofRaw = function (it) {
    return stringSlice(toString$1(it), 8, -1);
  };

  var global$n = global$o;
  var uncurryThis$6 = functionUncurryThis;
  var fails$4 = fails$6;
  var classof$1 = classofRaw;

  var Object$3 = global$n.Object;
  var split = uncurryThis$6(''.split);

  // fallback for non-array-like ES3 and non-enumerable old V8 strings
  var indexedObject = fails$4(function () {
    // throws an error in rhino, see https://github.com/mozilla/rhino/issues/346
    // eslint-disable-next-line no-prototype-builtins -- safe
    return !Object$3('z').propertyIsEnumerable(0);
  }) ? function (it) {
    return classof$1(it) == 'String' ? split(it, '') : Object$3(it);
  } : Object$3;

  var global$m = global$o;

  var TypeError$6 = global$m.TypeError;

  // `RequireObjectCoercible` abstract operation
  // https://tc39.es/ecma262/#sec-requireobjectcoercible
  var requireObjectCoercible$2 = function (it) {
    if (it == undefined) throw TypeError$6("Can't call method on " + it);
    return it;
  };

  // toObject with fallback for non-array-like ES3 strings
  var IndexedObject = indexedObject;
  var requireObjectCoercible$1 = requireObjectCoercible$2;

  var toIndexedObject$1 = function (it) {
    return IndexedObject(requireObjectCoercible$1(it));
  };

  var isCallable$7 = isCallable$8;

  var isObject$4 = function (it) {
    return typeof it == 'object' ? it !== null : isCallable$7(it);
  };

  var path$5 = {};

  var path$4 = path$5;
  var global$l = global$o;
  var isCallable$6 = isCallable$8;

  var aFunction = function (variable) {
    return isCallable$6(variable) ? variable : undefined;
  };

  var getBuiltIn$3 = function (namespace, method) {
    return arguments.length < 2 ? aFunction(path$4[namespace]) || aFunction(global$l[namespace])
      : path$4[namespace] && path$4[namespace][method] || global$l[namespace] && global$l[namespace][method];
  };

  var uncurryThis$5 = functionUncurryThis;

  var objectIsPrototypeOf = uncurryThis$5({}.isPrototypeOf);

  var getBuiltIn$2 = getBuiltIn$3;

  var engineUserAgent = getBuiltIn$2('navigator', 'userAgent') || '';

  var global$k = global$o;
  var userAgent$3 = engineUserAgent;

  var process$3 = global$k.process;
  var Deno = global$k.Deno;
  var versions = process$3 && process$3.versions || Deno && Deno.version;
  var v8 = versions && versions.v8;
  var match, version;

  if (v8) {
    match = v8.split('.');
    // in old Chrome, versions of V8 isn't V8 = Chrome / 10
    // but their correct versions are not interesting for us
    version = match[0] > 0 && match[0] < 4 ? 1 : +(match[0] + match[1]);
  }

  // BrowserFS NodeJS `process` polyfill incorrectly set `.v8` to `0.0`
  // so check `userAgent` even if `.v8` exists, but 0
  if (!version && userAgent$3) {
    match = userAgent$3.match(/Edge\/(\d+)/);
    if (!match || match[1] >= 74) {
      match = userAgent$3.match(/Chrome\/(\d+)/);
      if (match) version = +match[1];
    }
  }

  var engineV8Version = version;

  /* eslint-disable es/no-symbol -- required for testing */

  var V8_VERSION = engineV8Version;
  var fails$3 = fails$6;

  // eslint-disable-next-line es/no-object-getownpropertysymbols -- required for testing
  var nativeSymbol = !!Object.getOwnPropertySymbols && !fails$3(function () {
    var symbol = Symbol();
    // Chrome 38 Symbol has incorrect toString conversion
    // `get-own-property-symbols` polyfill symbols converted to object are not Symbol instances
    return !String(symbol) || !(Object(symbol) instanceof Symbol) ||
      // Chrome 38-40 symbols are not inherited from DOM collections prototypes to instances
      !Symbol.sham && V8_VERSION && V8_VERSION < 41;
  });

  /* eslint-disable es/no-symbol -- required for testing */

  var NATIVE_SYMBOL$1 = nativeSymbol;

  var useSymbolAsUid = NATIVE_SYMBOL$1
    && !Symbol.sham
    && typeof Symbol.iterator == 'symbol';

  var global$j = global$o;
  var getBuiltIn$1 = getBuiltIn$3;
  var isCallable$5 = isCallable$8;
  var isPrototypeOf = objectIsPrototypeOf;
  var USE_SYMBOL_AS_UID$1 = useSymbolAsUid;

  var Object$2 = global$j.Object;

  var isSymbol$2 = USE_SYMBOL_AS_UID$1 ? function (it) {
    return typeof it == 'symbol';
  } : function (it) {
    var $Symbol = getBuiltIn$1('Symbol');
    return isCallable$5($Symbol) && isPrototypeOf($Symbol.prototype, Object$2(it));
  };

  var global$i = global$o;

  var String$3 = global$i.String;

  var tryToString$1 = function (argument) {
    try {
      return String$3(argument);
    } catch (error) {
      return 'Object';
    }
  };

  var global$h = global$o;
  var isCallable$4 = isCallable$8;
  var tryToString = tryToString$1;

  var TypeError$5 = global$h.TypeError;

  // `Assert: IsCallable(argument) is true`
  var aCallable$2 = function (argument) {
    if (isCallable$4(argument)) return argument;
    throw TypeError$5(tryToString(argument) + ' is not a function');
  };

  var aCallable$1 = aCallable$2;

  // `GetMethod` abstract operation
  // https://tc39.es/ecma262/#sec-getmethod
  var getMethod$1 = function (V, P) {
    var func = V[P];
    return func == null ? undefined : aCallable$1(func);
  };

  var global$g = global$o;
  var call$2 = functionCall;
  var isCallable$3 = isCallable$8;
  var isObject$3 = isObject$4;

  var TypeError$4 = global$g.TypeError;

  // `OrdinaryToPrimitive` abstract operation
  // https://tc39.es/ecma262/#sec-ordinarytoprimitive
  var ordinaryToPrimitive$1 = function (input, pref) {
    var fn, val;
    if (pref === 'string' && isCallable$3(fn = input.toString) && !isObject$3(val = call$2(fn, input))) return val;
    if (isCallable$3(fn = input.valueOf) && !isObject$3(val = call$2(fn, input))) return val;
    if (pref !== 'string' && isCallable$3(fn = input.toString) && !isObject$3(val = call$2(fn, input))) return val;
    throw TypeError$4("Can't convert object to primitive value");
  };

  var shared$1 = {exports: {}};

  var global$f = global$o;

  // eslint-disable-next-line es/no-object-defineproperty -- safe
  var defineProperty = Object.defineProperty;

  var setGlobal$1 = function (key, value) {
    try {
      defineProperty(global$f, key, { value: value, configurable: true, writable: true });
    } catch (error) {
      global$f[key] = value;
    } return value;
  };

  var global$e = global$o;
  var setGlobal = setGlobal$1;

  var SHARED = '__core-js_shared__';
  var store$1 = global$e[SHARED] || setGlobal(SHARED, {});

  var sharedStore = store$1;

  var store = sharedStore;

  (shared$1.exports = function (key, value) {
    return store[key] || (store[key] = value !== undefined ? value : {});
  })('versions', []).push({
    version: '3.19.1',
    mode: 'pure' ,
    copyright: '© 2021 Denis Pushkarev (zloirock.ru)'
  });

  var global$d = global$o;
  var requireObjectCoercible = requireObjectCoercible$2;

  var Object$1 = global$d.Object;

  // `ToObject` abstract operation
  // https://tc39.es/ecma262/#sec-toobject
  var toObject$1 = function (argument) {
    return Object$1(requireObjectCoercible(argument));
  };

  var uncurryThis$4 = functionUncurryThis;
  var toObject = toObject$1;

  var hasOwnProperty = uncurryThis$4({}.hasOwnProperty);

  // `HasOwnProperty` abstract operation
  // https://tc39.es/ecma262/#sec-hasownproperty
  var hasOwnProperty_1 = Object.hasOwn || function hasOwn(it, key) {
    return hasOwnProperty(toObject(it), key);
  };

  var uncurryThis$3 = functionUncurryThis;

  var id = 0;
  var postfix = Math.random();
  var toString = uncurryThis$3(1.0.toString);

  var uid$1 = function (key) {
    return 'Symbol(' + (key === undefined ? '' : key) + ')_' + toString(++id + postfix, 36);
  };

  var global$c = global$o;
  var shared = shared$1.exports;
  var hasOwn$3 = hasOwnProperty_1;
  var uid = uid$1;
  var NATIVE_SYMBOL = nativeSymbol;
  var USE_SYMBOL_AS_UID = useSymbolAsUid;

  var WellKnownSymbolsStore = shared('wks');
  var Symbol$1 = global$c.Symbol;
  var symbolFor = Symbol$1 && Symbol$1['for'];
  var createWellKnownSymbol = USE_SYMBOL_AS_UID ? Symbol$1 : Symbol$1 && Symbol$1.withoutSetter || uid;

  var wellKnownSymbol$1 = function (name) {
    if (!hasOwn$3(WellKnownSymbolsStore, name) || !(NATIVE_SYMBOL || typeof WellKnownSymbolsStore[name] == 'string')) {
      var description = 'Symbol.' + name;
      if (NATIVE_SYMBOL && hasOwn$3(Symbol$1, name)) {
        WellKnownSymbolsStore[name] = Symbol$1[name];
      } else if (USE_SYMBOL_AS_UID && symbolFor) {
        WellKnownSymbolsStore[name] = symbolFor(description);
      } else {
        WellKnownSymbolsStore[name] = createWellKnownSymbol(description);
      }
    } return WellKnownSymbolsStore[name];
  };

  var global$b = global$o;
  var call$1 = functionCall;
  var isObject$2 = isObject$4;
  var isSymbol$1 = isSymbol$2;
  var getMethod = getMethod$1;
  var ordinaryToPrimitive = ordinaryToPrimitive$1;
  var wellKnownSymbol = wellKnownSymbol$1;

  var TypeError$3 = global$b.TypeError;
  var TO_PRIMITIVE = wellKnownSymbol('toPrimitive');

  // `ToPrimitive` abstract operation
  // https://tc39.es/ecma262/#sec-toprimitive
  var toPrimitive$1 = function (input, pref) {
    if (!isObject$2(input) || isSymbol$1(input)) return input;
    var exoticToPrim = getMethod(input, TO_PRIMITIVE);
    var result;
    if (exoticToPrim) {
      if (pref === undefined) pref = 'default';
      result = call$1(exoticToPrim, input, pref);
      if (!isObject$2(result) || isSymbol$1(result)) return result;
      throw TypeError$3("Can't convert object to primitive value");
    }
    if (pref === undefined) pref = 'number';
    return ordinaryToPrimitive(input, pref);
  };

  var toPrimitive = toPrimitive$1;
  var isSymbol = isSymbol$2;

  // `ToPropertyKey` abstract operation
  // https://tc39.es/ecma262/#sec-topropertykey
  var toPropertyKey$2 = function (argument) {
    var key = toPrimitive(argument, 'string');
    return isSymbol(key) ? key : key + '';
  };

  var global$a = global$o;
  var isObject$1 = isObject$4;

  var document$1 = global$a.document;
  // typeof document.createElement is 'object' in old IE
  var EXISTS = isObject$1(document$1) && isObject$1(document$1.createElement);

  var documentCreateElement = function (it) {
    return EXISTS ? document$1.createElement(it) : {};
  };

  var DESCRIPTORS$3 = descriptors;
  var fails$2 = fails$6;
  var createElement$1 = documentCreateElement;

  // Thank's IE8 for his funny defineProperty
  var ie8DomDefine = !DESCRIPTORS$3 && !fails$2(function () {
    // eslint-disable-next-line es/no-object-defineproperty -- requied for testing
    return Object.defineProperty(createElement$1('div'), 'a', {
      get: function () { return 7; }
    }).a != 7;
  });

  var DESCRIPTORS$2 = descriptors;
  var call = functionCall;
  var propertyIsEnumerableModule = objectPropertyIsEnumerable;
  var createPropertyDescriptor$1 = createPropertyDescriptor$2;
  var toIndexedObject = toIndexedObject$1;
  var toPropertyKey$1 = toPropertyKey$2;
  var hasOwn$2 = hasOwnProperty_1;
  var IE8_DOM_DEFINE$1 = ie8DomDefine;

  // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
  var $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;

  // `Object.getOwnPropertyDescriptor` method
  // https://tc39.es/ecma262/#sec-object.getownpropertydescriptor
  objectGetOwnPropertyDescriptor.f = DESCRIPTORS$2 ? $getOwnPropertyDescriptor : function getOwnPropertyDescriptor(O, P) {
    O = toIndexedObject(O);
    P = toPropertyKey$1(P);
    if (IE8_DOM_DEFINE$1) try {
      return $getOwnPropertyDescriptor(O, P);
    } catch (error) { /* empty */ }
    if (hasOwn$2(O, P)) return createPropertyDescriptor$1(!call(propertyIsEnumerableModule.f, O, P), O[P]);
  };

  var fails$1 = fails$6;
  var isCallable$2 = isCallable$8;

  var replacement = /#|\.prototype\./;

  var isForced$1 = function (feature, detection) {
    var value = data[normalize(feature)];
    return value == POLYFILL ? true
      : value == NATIVE ? false
      : isCallable$2(detection) ? fails$1(detection)
      : !!detection;
  };

  var normalize = isForced$1.normalize = function (string) {
    return String(string).replace(replacement, '.').toLowerCase();
  };

  var data = isForced$1.data = {};
  var NATIVE = isForced$1.NATIVE = 'N';
  var POLYFILL = isForced$1.POLYFILL = 'P';

  var isForced_1 = isForced$1;

  var uncurryThis$2 = functionUncurryThis;
  var aCallable = aCallable$2;

  var bind$3 = uncurryThis$2(uncurryThis$2.bind);

  // optional / simple context binding
  var functionBindContext = function (fn, that) {
    aCallable(fn);
    return that === undefined ? fn : bind$3 ? bind$3(fn, that) : function (/* ...args */) {
      return fn.apply(that, arguments);
    };
  };

  var objectDefineProperty = {};

  var global$9 = global$o;
  var isObject = isObject$4;

  var String$2 = global$9.String;
  var TypeError$2 = global$9.TypeError;

  // `Assert: Type(argument) is Object`
  var anObject$1 = function (argument) {
    if (isObject(argument)) return argument;
    throw TypeError$2(String$2(argument) + ' is not an object');
  };

  var global$8 = global$o;
  var DESCRIPTORS$1 = descriptors;
  var IE8_DOM_DEFINE = ie8DomDefine;
  var anObject = anObject$1;
  var toPropertyKey = toPropertyKey$2;

  var TypeError$1 = global$8.TypeError;
  // eslint-disable-next-line es/no-object-defineproperty -- safe
  var $defineProperty = Object.defineProperty;

  // `Object.defineProperty` method
  // https://tc39.es/ecma262/#sec-object.defineproperty
  objectDefineProperty.f = DESCRIPTORS$1 ? $defineProperty : function defineProperty(O, P, Attributes) {
    anObject(O);
    P = toPropertyKey(P);
    anObject(Attributes);
    if (IE8_DOM_DEFINE) try {
      return $defineProperty(O, P, Attributes);
    } catch (error) { /* empty */ }
    if ('get' in Attributes || 'set' in Attributes) throw TypeError$1('Accessors not supported');
    if ('value' in Attributes) O[P] = Attributes.value;
    return O;
  };

  var DESCRIPTORS = descriptors;
  var definePropertyModule = objectDefineProperty;
  var createPropertyDescriptor = createPropertyDescriptor$2;

  var createNonEnumerableProperty$1 = DESCRIPTORS ? function (object, key, value) {
    return definePropertyModule.f(object, key, createPropertyDescriptor(1, value));
  } : function (object, key, value) {
    object[key] = value;
    return object;
  };

  var global$7 = global$o;
  var apply$1 = functionApply;
  var uncurryThis$1 = functionUncurryThis;
  var isCallable$1 = isCallable$8;
  var getOwnPropertyDescriptor$1 = objectGetOwnPropertyDescriptor.f;
  var isForced = isForced_1;
  var path$3 = path$5;
  var bind$2 = functionBindContext;
  var createNonEnumerableProperty = createNonEnumerableProperty$1;
  var hasOwn$1 = hasOwnProperty_1;

  var wrapConstructor = function (NativeConstructor) {
    var Wrapper = function (a, b, c) {
      if (this instanceof Wrapper) {
        switch (arguments.length) {
          case 0: return new NativeConstructor();
          case 1: return new NativeConstructor(a);
          case 2: return new NativeConstructor(a, b);
        } return new NativeConstructor(a, b, c);
      } return apply$1(NativeConstructor, this, arguments);
    };
    Wrapper.prototype = NativeConstructor.prototype;
    return Wrapper;
  };

  /*
    options.target      - name of the target object
    options.global      - target is the global object
    options.stat        - export as static methods of target
    options.proto       - export as prototype methods of target
    options.real        - real prototype method for the `pure` version
    options.forced      - export even if the native feature is available
    options.bind        - bind methods to the target, required for the `pure` version
    options.wrap        - wrap constructors to preventing global pollution, required for the `pure` version
    options.unsafe      - use the simple assignment of property instead of delete + defineProperty
    options.sham        - add a flag to not completely full polyfills
    options.enumerable  - export as enumerable property
    options.noTargetGet - prevent calling a getter on target
    options.name        - the .name of the function if it does not match the key
  */
  var _export = function (options, source) {
    var TARGET = options.target;
    var GLOBAL = options.global;
    var STATIC = options.stat;
    var PROTO = options.proto;

    var nativeSource = GLOBAL ? global$7 : STATIC ? global$7[TARGET] : (global$7[TARGET] || {}).prototype;

    var target = GLOBAL ? path$3 : path$3[TARGET] || createNonEnumerableProperty(path$3, TARGET, {})[TARGET];
    var targetPrototype = target.prototype;

    var FORCED, USE_NATIVE, VIRTUAL_PROTOTYPE;
    var key, sourceProperty, targetProperty, nativeProperty, resultProperty, descriptor;

    for (key in source) {
      FORCED = isForced(GLOBAL ? key : TARGET + (STATIC ? '.' : '#') + key, options.forced);
      // contains in native
      USE_NATIVE = !FORCED && nativeSource && hasOwn$1(nativeSource, key);

      targetProperty = target[key];

      if (USE_NATIVE) if (options.noTargetGet) {
        descriptor = getOwnPropertyDescriptor$1(nativeSource, key);
        nativeProperty = descriptor && descriptor.value;
      } else nativeProperty = nativeSource[key];

      // export native or implementation
      sourceProperty = (USE_NATIVE && nativeProperty) ? nativeProperty : source[key];

      if (USE_NATIVE && typeof targetProperty == typeof sourceProperty) continue;

      // bind timers to global for call from export context
      if (options.bind && USE_NATIVE) resultProperty = bind$2(sourceProperty, global$7);
      // wrap global constructors for prevent changs in this version
      else if (options.wrap && USE_NATIVE) resultProperty = wrapConstructor(sourceProperty);
      // make static versions for prototype methods
      else if (PROTO && isCallable$1(sourceProperty)) resultProperty = uncurryThis$1(sourceProperty);
      // default case
      else resultProperty = sourceProperty;

      // add a flag to not completely full polyfills
      if (options.sham || (sourceProperty && sourceProperty.sham) || (targetProperty && targetProperty.sham)) {
        createNonEnumerableProperty(resultProperty, 'sham', true);
      }

      createNonEnumerableProperty(target, key, resultProperty);

      if (PROTO) {
        VIRTUAL_PROTOTYPE = TARGET + 'Prototype';
        if (!hasOwn$1(path$3, VIRTUAL_PROTOTYPE)) {
          createNonEnumerableProperty(path$3, VIRTUAL_PROTOTYPE, {});
        }
        // export virtual prototype methods
        createNonEnumerableProperty(path$3[VIRTUAL_PROTOTYPE], key, sourceProperty);
        // export real prototype methods
        if (options.real && targetPrototype && !targetPrototype[key]) {
          createNonEnumerableProperty(targetPrototype, key, sourceProperty);
        }
      }
    }
  };

  var getBuiltIn = getBuiltIn$3;

  var html$1 = getBuiltIn('document', 'documentElement');

  var uncurryThis = functionUncurryThis;

  var arraySlice$1 = uncurryThis([].slice);

  var userAgent$2 = engineUserAgent;

  var engineIsIos = /(?:ipad|iphone|ipod).*applewebkit/i.test(userAgent$2);

  var classof = classofRaw;
  var global$6 = global$o;

  var engineIsNode = classof(global$6.process) == 'process';

  var global$5 = global$o;
  var apply = functionApply;
  var bind$1 = functionBindContext;
  var isCallable = isCallable$8;
  var hasOwn = hasOwnProperty_1;
  var fails = fails$6;
  var html = html$1;
  var arraySlice = arraySlice$1;
  var createElement = documentCreateElement;
  var IS_IOS$1 = engineIsIos;
  var IS_NODE$2 = engineIsNode;

  var set = global$5.setImmediate;
  var clear = global$5.clearImmediate;
  var process$2 = global$5.process;
  var Dispatch = global$5.Dispatch;
  var Function$1 = global$5.Function;
  var MessageChannel = global$5.MessageChannel;
  var String$1 = global$5.String;
  var counter = 0;
  var queue = {};
  var ONREADYSTATECHANGE = 'onreadystatechange';
  var location, defer, channel, port;

  try {
    // Deno throws a ReferenceError on `location` access without `--location` flag
    location = global$5.location;
  } catch (error) { /* empty */ }

  var run = function (id) {
    if (hasOwn(queue, id)) {
      var fn = queue[id];
      delete queue[id];
      fn();
    }
  };

  var runner = function (id) {
    return function () {
      run(id);
    };
  };

  var listener = function (event) {
    run(event.data);
  };

  var post = function (id) {
    // old engines have not location.origin
    global$5.postMessage(String$1(id), location.protocol + '//' + location.host);
  };

  // Node.js 0.9+ & IE10+ has setImmediate, otherwise:
  if (!set || !clear) {
    set = function setImmediate(fn) {
      var args = arraySlice(arguments, 1);
      queue[++counter] = function () {
        apply(isCallable(fn) ? fn : Function$1(fn), undefined, args);
      };
      defer(counter);
      return counter;
    };
    clear = function clearImmediate(id) {
      delete queue[id];
    };
    // Node.js 0.8-
    if (IS_NODE$2) {
      defer = function (id) {
        process$2.nextTick(runner(id));
      };
    // Sphere (JS game engine) Dispatch API
    } else if (Dispatch && Dispatch.now) {
      defer = function (id) {
        Dispatch.now(runner(id));
      };
    // Browsers with MessageChannel, includes WebWorkers
    // except iOS - https://github.com/zloirock/core-js/issues/624
    } else if (MessageChannel && !IS_IOS$1) {
      channel = new MessageChannel();
      port = channel.port2;
      channel.port1.onmessage = listener;
      defer = bind$1(port.postMessage, port);
    // Browsers with postMessage, skip WebWorkers
    // IE8 has postMessage, but it's sync & typeof its postMessage is 'object'
    } else if (
      global$5.addEventListener &&
      isCallable(global$5.postMessage) &&
      !global$5.importScripts &&
      location && location.protocol !== 'file:' &&
      !fails(post)
    ) {
      defer = post;
      global$5.addEventListener('message', listener, false);
    // IE8-
    } else if (ONREADYSTATECHANGE in createElement('script')) {
      defer = function (id) {
        html.appendChild(createElement('script'))[ONREADYSTATECHANGE] = function () {
          html.removeChild(this);
          run(id);
        };
      };
    // Rest old browsers
    } else {
      defer = function (id) {
        setTimeout(runner(id), 0);
      };
    }
  }

  var task$1 = {
    set: set,
    clear: clear
  };

  var $$1 = _export;
  var global$4 = global$o;
  var task = task$1;

  var FORCED = !global$4.setImmediate || !global$4.clearImmediate;

  // http://w3c.github.io/setImmediate/
  $$1({ global: true, bind: true, enumerable: true, forced: FORCED }, {
    // `setImmediate` method
    // http://w3c.github.io/setImmediate/#si-setImmediate
    setImmediate: task.set,
    // `clearImmediate` method
    // http://w3c.github.io/setImmediate/#si-clearImmediate
    clearImmediate: task.clear
  });

  var path$2 = path$5;

  var setImmediate$1 = path$2.setImmediate;

  var parent$3 = setImmediate$1;

  var setImmediate = parent$3;

  var path$1 = path$5;

  var clearImmediate$1 = path$1.clearImmediate;

  var parent$2 = clearImmediate$1;

  var clearImmediate = parent$2;

  /**
   * A function returning a promise that will be resolved in a later tick of the event loop.
   *
   * This function returns both a promise and cancel function in order to cancel the wait time if
   * necessary. If cancelled, the promise will be rejected with a CancelledError.
   *
   * This function uses core-js' shim for `setImmediate()` internally.
   *
   * @returns {Array} A tuple of two objects:
   *   * The promise
   *   * The cancel function. It will return a boolean that will be true if the promise was effectively cancelled,
   *     false otherwise.
   * @example
   * import { asyncDelayCancellable, CancelledError } from 'modern-async'
   *
   * const [promise, cancel] = asyncDelayCancellable()
   * cancel()
   * try {
   *   await promise
   * } catch (e) {
   *   console.log(e instanceof CancelledError) // prints true
   * }
   */
  function asyncDelayCancellable () {
    const deferred = new Deferred();
    const id = setImmediate(deferred.resolve);
    let terminated = false;
    return [deferred.promise.finally(() => {
      terminated = true;
    }), () => {
      if (terminated) {
        return false
      } else {
        terminated = true;
        deferred.reject(new CancelledError());
        clearImmediate(id);
        return true
      }
    }]
  }

  /**
   * A function returning a promise that will be resolved in a later task of the event loop.
   *
   * This function uses core-js' shim for `setImmediate()` internally.
   *
   * @returns {Promise<void>} A promise that will be resolved on a later tick of the event loop.
   * @example
   * import { asyncDelay } from 'modern-async'
   *
   * console.log('this executes in a tick of the event loop')
   * await asyncDelay()
   * console.log('this executes in another tick of the event loop')
   */
  async function asyncDelay () {
    return asyncDelayCancellable()[0]
  }

  /**
   * A class used to spread time or cpu intensive operations on multiple tasks in the event loop in order
   * to avoid blocking other tasks that may need to be executed.
   *
   * It is configured with a trigger time, which represents the maximum amount of time your tasks should
   * monopolize the event loop. Choosing an appropriate trigger time is both important and hard. If too low
   * it will impact the performances of your long running algorithm. If too high it will impact the other
   * tasks that need to run in the event loop.
   *
   * When using Delayer your code should contain frequent calls to `await delayer.checkDelay()`, usually
   * at the end of every loop. `checkDelay()` will check the amount of time that ellasped since the last time
   * it triggered a new task in the event loop. If the amount of time is below the trigger time it returns
   * an already resolved promise and the remaining computation will be able to continue processing in a
   * microtask. If not it will call the `asyncDelay()` function that will retrigger the operation in a later task
   * of the event loop.
   *
   * @example
   * import { Delayer } from 'modern-async'
   *
   * const delayer = new Delayer(10) // a delayer with 10ms trigger time
   *
   * // some cpu intensive operation that will run for a long time
   * for (let i = 0; i < 100000000; i += 1) {
   *   // some code
   *   await delayer.checkDelay()
   * }
   */
  class Delayer {
    /**
     * Constructs a new `Delayer` by specifying its trigger time.
     *
     * @param {number} triggerTime The trigger time.
     */
    constructor (triggerTime) {
      this.triggerTime = triggerTime;
      this.reset();
    }

    /**
     * The trigger time of this `Delayer` in milliseconds. The trigger time represent the
     * maximum amount of time before a call to `checkDelay()` decide to schedule a new task in the event loop.
     *
     * @member {number}
     * @returns {number} ignore
     */
    get triggerTime () {
      return this._triggerTime
    }

    /**
     * @ignore
     * @param {number} triggerTime ignore
     */
    set triggerTime (triggerTime) {
      nanoassert(typeof triggerTime === 'number', 'trigger time must be a number');
      this._triggerTime = triggerTime;
    }

    /**
     * Resets the internal timer to the current time.
     */
    reset () {
      this._last = new Date().getTime();
    }

    /**
     * Checks if a delay must be applied according to the internal timer. If that's the case this method
     * will call `asyncDelay()` and return `true`. If not it will do nothing and return `false`.
     *
     * @returns {boolean} `true` if a new task was scheduled in the event loop, `false` otherwise.
     */
    async checkDelay () {
      const current = new Date().getTime();
      if (current - this._last >= this.triggerTime) {
        await asyncDelay();
        this.reset();
        return true
      } else {
        return false
      }
    }
  }

  var userAgent$1 = engineUserAgent;
  var global$3 = global$o;

  var engineIsIosPebble = /ipad|iphone|ipod/i.test(userAgent$1) && global$3.Pebble !== undefined;

  var userAgent = engineUserAgent;

  var engineIsWebosWebkit = /web0s(?!.*chrome)/i.test(userAgent);

  var global$2 = global$o;
  var bind = functionBindContext;
  var getOwnPropertyDescriptor = objectGetOwnPropertyDescriptor.f;
  var macrotask = task$1.set;
  var IS_IOS = engineIsIos;
  var IS_IOS_PEBBLE = engineIsIosPebble;
  var IS_WEBOS_WEBKIT = engineIsWebosWebkit;
  var IS_NODE$1 = engineIsNode;

  var MutationObserver = global$2.MutationObserver || global$2.WebKitMutationObserver;
  var document = global$2.document;
  var process$1 = global$2.process;
  var Promise$1 = global$2.Promise;
  // Node.js 11 shows ExperimentalWarning on getting `queueMicrotask`
  var queueMicrotaskDescriptor = getOwnPropertyDescriptor(global$2, 'queueMicrotask');
  var queueMicrotask$4 = queueMicrotaskDescriptor && queueMicrotaskDescriptor.value;

  var flush, head, last, notify, toggle, node, promise, then;

  // modern engines have queueMicrotask method
  if (!queueMicrotask$4) {
    flush = function () {
      var parent, fn;
      if (IS_NODE$1 && (parent = process$1.domain)) parent.exit();
      while (head) {
        fn = head.fn;
        head = head.next;
        try {
          fn();
        } catch (error) {
          if (head) notify();
          else last = undefined;
          throw error;
        }
      } last = undefined;
      if (parent) parent.enter();
    };

    // browsers with MutationObserver, except iOS - https://github.com/zloirock/core-js/issues/339
    // also except WebOS Webkit https://github.com/zloirock/core-js/issues/898
    if (!IS_IOS && !IS_NODE$1 && !IS_WEBOS_WEBKIT && MutationObserver && document) {
      toggle = true;
      node = document.createTextNode('');
      new MutationObserver(flush).observe(node, { characterData: true });
      notify = function () {
        node.data = toggle = !toggle;
      };
    // environments with maybe non-completely correct, but existent Promise
    } else if (!IS_IOS_PEBBLE && Promise$1 && Promise$1.resolve) {
      // Promise.resolve without an argument throws an error in LG WebOS 2
      promise = Promise$1.resolve(undefined);
      // workaround of WebKit ~ iOS Safari 10.1 bug
      promise.constructor = Promise$1;
      then = bind(promise.then, promise);
      notify = function () {
        then(flush);
      };
    // Node.js without promises
    } else if (IS_NODE$1) {
      notify = function () {
        process$1.nextTick(flush);
      };
    // for other environments - macrotask based on:
    // - setImmediate
    // - MessageChannel
    // - window.postMessag
    // - onreadystatechange
    // - setTimeout
    } else {
      // strange IE + webpack dev server bug - use .bind(global)
      macrotask = bind(macrotask, global$2);
      notify = function () {
        macrotask(flush);
      };
    }
  }

  var microtask$1 = queueMicrotask$4 || function (fn) {
    var task = { fn: fn, next: undefined };
    if (last) last.next = task;
    if (!head) {
      head = task;
      notify();
    } last = task;
  };

  var $ = _export;
  var global$1 = global$o;
  var microtask = microtask$1;
  var IS_NODE = engineIsNode;

  var process = global$1.process;

  // `queueMicrotask` method
  // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-queuemicrotask
  $({ global: true, enumerable: true, noTargetGet: true }, {
    queueMicrotask: function queueMicrotask(fn) {
      var domain = IS_NODE && process.domain;
      microtask(domain ? domain.bind(fn) : fn);
    }
  });

  var path = path$5;

  var queueMicrotask$3 = path.queueMicrotask;

  var parent$1 = queueMicrotask$3;

  var queueMicrotask$2 = parent$1;

  var parent = queueMicrotask$2;

  var queueMicrotask$1 = parent;

  /**
   * An alternative to standard `queueMicrotask()` function.
   *
   * This is just of mirror of core-js' implementation for compatibility.
   *
   * @param {Function} fct The function to call in a microtask.
   * @example
   * import { queueMicrotask } from 'modern-async'
   *
   * queueMicrotask(() => {
   *   console.log('this resolves in a micro task')
   * })
   */
  function queueMicrotask (fct) {
    queueMicrotask$1(fct);
  }

  /**
   * A class representing a queue.
   *
   * Tasks added to the queue are processed in parallel (up to the concurrency limit).
   * If all slots of the queue are occupied, the task is queued until one becomes available.
   * When a slot is freed, the pending task with higher priority is executed. If multiple pending tasks have the same
   * priority the first that was scheduled is executed.
   *
   * Once a task is completed, its corresponding promise is terminated accordingly.
   *
   * @example
   * import { Queue, asyncSleep } from 'modern-async'
   *
   * const queue = new Queue(3) // create a queue with concurrency 3
   *
   * const array = Array.from(Array(100).keys()) // an array of 100 numbers from 0 to 99
   *
   * const promises = []
   * for (const i of array) {
   *   promises.push(queue.exec(async () => {
   *     console.log(`Starting task ${i}`)
   *     await asyncSleep(Math.random() * 10) // waits a random amount of time between 0ms and 10ms
   *     console.log(`Ending task ${i}`)
   *     return i;
   *   }))
   * }
   * const results = await Promise.all(promises)
   * // all the scheduled tasks will perform with a maximum concurrency of 3 and log when they start and stop
   *
   * console.log(results) // will display an array with the result of the execution of each separate task
   */
  class Queue {
    /**
     * Constructs a queue with the given concurrency
     *
     * @param {number} concurrency The concurrency of the queue, must be an integer greater than 0 or
     * `Number.POSITIVE_INFINITY`.
     */
    constructor (concurrency) {
      nanoassert(Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY,
        'concurrency must be an integer or positive infinity');
      nanoassert(concurrency > 0, 'concurrency must be greater than 0');
      this._concurrency = concurrency;
      this._iqueue = [];
      this._running = 0;
      this._checkQueueScheduled = false;
    }

    /**
     * (Read-only) The concurrency of the queue.
     *
     * @member {number}
     * @returns {number} ignore
     */
    get concurrency () {
      return this._concurrency
    }

    /**
     * (Read-only) The current number of tasks that are processing.
     *
     * @member {number}
     * @returns {number} ignore
     */
    get running () {
      return this._running
    }

    /**
     * (Read-only) The number of pending tasks.
     *
     * @member {number}
     * @returns {number} ignore
     */
    get pending () {
      return this._iqueue.length - this.running
    }

    /**
     * Puts a task at the end of the queue. When the task is executed and completes the returned promise will be terminated
     * accordingly.
     *
     * @param {Function} fct An asynchronous functions representing the task. It will be executed when the queue has
     * available slots and its result will be propagated to the promise returned by exec().
     * @param {number} [priority] The priority of the task. The higher the priority is, the sooner the task will be
     * executed regarding the priority of other pending tasks. Defaults to 0.
     * @returns {Promise} A promise that will be resolved or rejected once the task has completed. Its state will be the same
     * than the promise returned by the call to `fct`.
     */
    async exec (fct, priority = 0) {
      return this.execCancellable(fct, priority)[0]
    }

    /**
     * Puts a task at the end of the queue. When the task is executed and completes the returned promise will be terminated
     * accordingly.
     *
     * This function returns both a promise and a cancel function. The cancel function allows to cancel the pending task,
     * but only if it wasn't started yet. Calling the cancel function on a task that it already running has no effect.
     * When a task is cancelled its corresponding promise will be rejected with a `CancelledError`.
     *
     * @param {Function} fct An asynchronous functions representing the task. It will be executed when the queue has
     * available slots and its result will be propagated to the promise returned by exec().
     * @param {number} [priority] The priority of the task. The higher the priority is, the sooner the task will be
     * executed regarding the priority of other pending tasks. Defaults to 0.
     * @returns {Array} A tuple with two parameters:
     *   * `promise`: A promise that will be resolved or rejected once the task has completed. Its state will be the same
     *     than the promise returned by the call to `fct`.
     *   * `cancel`: A cancel function. When called it will cancel the task if it is still pending. It has no effect is the
     *     task has already started or already terminated. When a task is cancelled its corresponding promise will be
     *     rejected with a `CancelledError`. If will return `true` if the task was effectively pending and was cancelled,
     *     `false` in any other case.
     */
    execCancellable (fct, priority = 0) {
      nanoassert(typeof fct === 'function', 'fct must be a function');
      nanoassert(typeof priority === 'number', 'priority must be a number');
      const deferred = new Deferred();
      let i = this._iqueue.length;
      while (i >= 1) {
        const t = this._iqueue[i - 1];
        if (t.priority >= priority) {
          break
        }
        i -= 1;
      }
      const task = {
        asyncFct: asyncWrap(fct),
        deferred,
        priority,
        state: 'pending'
      };
      this._iqueue.splice(i, 0, task);
      this._scheduleCheckQueue();
      return [deferred.promise, () => {
        if (task.state !== 'pending') {
          return false
        } else {
          const filtered = this._iqueue.filter((v) => v !== task);
          nanoassert(filtered.length < this._iqueue.length);
          this._iqueue = filtered;
          task.state = 'cancelled';
          deferred.reject(new CancelledError());
          return true
        }
      }]
    }

    /**
     * @ignore
     */
    _scheduleCheckQueue () {
      if (this._checkQueueScheduled) {
        return
      }
      this._checkQueueScheduled = true;
      queueMicrotask(() => {
        this._checkQueueScheduled = false;
        this._checkQueue();
      });
    }

    /**
     * @ignore
     */
    _checkQueue () {
      while (true) {
        nanoassert(this.running >= 0, 'invalid state');
        nanoassert(this.running <= this.concurrency, 'invalid state');
        if (this.running === this.concurrency) {
          return
        }
        const task = this._iqueue.find((v) => v.state === 'pending');
        if (task === undefined) {
          return
        }
        this._running += 1;
        task.state = 'running';
        queueMicrotask(() => {
          task.asyncFct().finally(() => {
            this._running -= 1;
            this._iqueue = this._iqueue.filter((v) => v !== task);
          }).then(task.deferred.resolve, task.deferred.reject).then(() => {
            this._scheduleCheckQueue();
          });
        });
      }
    }

    /**
     * Cancels all pending tasks. Their corresponding promises will be rejected with a `CancelledError`. This method will
     * not alter tasks that are already running.
     *
     * @returns {number} The number of pending tasks that were effectively cancelled.
     */
    cancelAllPending () {
      const toCancel = this._iqueue.filter((task) => task.state === 'pending');
      this._iqueue = this._iqueue.filter((task) => task.state !== 'pending');
      toCancel.forEach((task) => {
        task.deferred.reject(new CancelledError());
      });
      return toCancel.length
    }
  }

  /**
   * @ignore
   * @param {*} queueOrConcurrency ignore
   * @returns {*} ignore
   */
  function getQueue (queueOrConcurrency) {
    if (typeof queueOrConcurrency === 'number') {
      return new Queue(queueOrConcurrency)
    } else {
      return queueOrConcurrency
    }
  }

  /**
   * Immediately calls an asynchronous function and wraps its result into a promise that
   * can only be resolved, not rejected, regardless of the state of the promised returned
   * by the function.
   *
   * The returned promise will contain an object with the following fields:
   *
   * * `status`: A string, either "fulfilled" or "rejected", indicating the state of the
   *   original promise.
   * * `value`: Only present if status is "fulfilled". The value that the promise was
   *   fulfilled with.
   * * `reason`: Only present if status is "rejected". The reason that the promise was
   *   rejected with.
   *
   * This object structure is similar to the one used by the [`Promise.allSettled()`
   * function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled).
   *
   * This function can be useful to make use of other functions in a fault-tolerant way.
   *
   * @param {Function} fct An asynchronous function
   * @returns {Promise<any>} A promise that will always be resolved with an object containing
   * a snapshot of the original promise state.
   * @example
   * import { reflectAsyncStatus, asyncMap, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   *
   * const result = await asyncMap(array, (v) => reflectAsyncStatus(async () => {
   *   await asyncSleep(10) // waits 10ms
   *   if (v % 2 === 0) { // throws error on some values
   *     throw Error("error")
   *   }
   *   return v
   * }))
   *
   * console.log(result)
   * // prints:
   * // [
   * //   { status: 'fulfilled', value: 1 },
   * //   { status: 'rejected', reason: Error: error },
   * //   { status: 'fulfilled', value: 3 }
   * // ]
   */
  async function reflectAsyncStatus (fct) {
    try {
      const res = await fct();
      return {
        status: 'fulfilled',
        value: res
      }
    } catch (e) {
      return {
        status: 'rejected',
        reason: e
      }
    }
  }

  /**
   * @ignore
   * @param {*} iterable ignore
   * @param {*} iteratee ignore
   * @param {*} queueOrConcurrency ignore
   * @param {*} ordered ignore
   * @returns {*} ignore
   */
  async function asyncFindInternal (iterable, iteratee, queueOrConcurrency, ordered) {
    nanoassert(typeof iteratee === 'function', 'iteratee must be a function');
    iteratee = asyncWrap(iteratee);
    const it = asyncIterableWrap(iterable);
    const queue = getQueue(queueOrConcurrency);

    /**
     * @ignore
     */
    class CustomCancelledError extends Error {}

    let lastIndexFetched = -1;
    let fetching = false;
    let hasFetchedValue = false;
    let fetchedValue = null;
    let exhausted = false;
    let shouldStop = false;

    let lastIndexHandled = -1;
    const results = [];

    let waitListIndex = 0;
    const waitList = new Map();
    const addToWaitList = (fct) => {
      const identifier = waitListIndex;
      waitListIndex += 1;
      const p = (async () => {
        return [identifier, await reflectAsyncStatus(fct)]
      })();
      nanoassert(!waitList.has(identifier), 'waitList already contains identifier');
      waitList.set(identifier, p);
    };
    const raceWaitList = async () => {
      nanoassert(waitList.size >= 1, 'Can not race on empty list');
      const [identifier] = await Promise.race([...waitList.values()]);
      const removed = waitList.delete(identifier);
      nanoassert(removed, 'waitList does not contain identifier');
    };

    let scheduledCount = 0;
    const scheduledList = new Map();
    const schedule = (index, value) => {
      scheduledCount += 1;
      const task = {
        value,
        index,
        cancel: null,
        state: null
      };
      scheduledList.set(index, task);
      addToWaitList(async () => {
        const p = queue.exec(async () => {
          if (task.state === 'cancelled') {
            throw new CustomCancelledError()
          }
          nanoassert(task.state === 'scheduled', 'invalid task state');
          const removed = scheduledList.delete(index);
          nanoassert(removed, 'Couldn\'t find index in scheduledList for removal');

          const snapshot = await reflectAsyncStatus(() => iteratee(value, index, iterable));

          scheduledCount -= 1;
          insertInResults(index, value, snapshot);
          if (snapshot.status === 'rejected' || (snapshot.status === 'fulfilled' && snapshot.value)) {
            shouldStop = true;
            cancelAllScheduled(ordered ? index : 0);
          }
        });
        nanoassert(task.cancel === null, 'task already has cancel');
        task.cancel = () => {
          nanoassert(task.state === 'scheduled', 'task should be scheduled');
          task.state = 'cancelled';
        };
        nanoassert(task.state === null, 'task should have no state');
        task.state = 'scheduled';
        return p
      });
    };
    const cancelAllScheduled = (fromIndex) => {
      for (const index of [...scheduledList.keys()].filter((el) => el >= fromIndex)) {
        const task = scheduledList.get(index);
        nanoassert(task.cancel, 'task does not have cancel');
        task.cancel();
        const removed = scheduledList.delete(index);
        nanoassert(removed, 'Couldn\'t find index in scheduledList for removal');
      }
    };
    const fetch = () => {
      fetching = true;
      addToWaitList(async () => {
        const snapshot = await reflectAsyncStatus(() => it.next());
        fetching = false;
        if (snapshot.status === 'fulfilled') {
          const { value, done } = snapshot.value;
          if (!done) {
            lastIndexFetched += 1;
            nanoassert(fetchedValue === null, 'fetchedValue should be null');
            fetchedValue = value;
            nanoassert(!hasFetchedValue, 'hasFetchedValue should be false');
            hasFetchedValue = true;
          } else {
            exhausted = true;
          }
        } else {
          exhausted = true;
          lastIndexFetched += 1;
          const index = lastIndexFetched;
          insertInResults(index, undefined, snapshot);
          cancelAllScheduled(ordered ? index : 0);
        }
      });
    };

    const insertInResults = (index, value, snapshot) => {
      if (ordered) {
        nanoassert(index - lastIndexHandled - 1 >= 0, 'invalid index to insert');
        nanoassert(results[index - lastIndexHandled - 1] === undefined, 'already inserted result');
        results[index - lastIndexHandled - 1] = { index, value, snapshot };
      } else {
        results.push({ index, value, snapshot });
      }
    };

    fetch();
    while (true) {
      await raceWaitList();
      while (results.length >= 1 && results[0] !== undefined) {
        const result = results.shift();
        lastIndexHandled += 1;
        if (result.snapshot.status === 'rejected') {
          throw result.snapshot.reason
        } else if (result.snapshot.value) {
          return [result.index, result.value]
        }
      }
      if (exhausted && lastIndexFetched === lastIndexHandled) {
        return [-1, undefined]
      }
      if (hasFetchedValue && !shouldStop) {
        schedule(lastIndexFetched, fetchedValue);
        hasFetchedValue = false;
        fetchedValue = null;
      }
      if (!fetching && !exhausted && !shouldStop && scheduledCount < queue.concurrency) {
        fetch();
      }
    }
  }

  /**
   * Returns the index of the first element of an iterable that passes an asynchronous truth test.
   *
   * The calls to `iteratee` will be performed in a queue to limit the concurrency of these calls.
   *
   * Whenever a result is found, all the remaining tasks will be cancelled as long
   * as they didn't started already. In case of exception in one of the iteratee calls the promise
   * returned by this function will be rejected with the exception and the remaining pending
   * tasks will also be cancelled. In the very specific case where a result is found and an
   * already started task throws an exception that exception will be plainly ignored.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
   * @param {Function} iteratee A function that will be called with each member of the iterable. It will receive
   * three arguments:
   *   * `value`: The current value to process
   *   * `index`: The index in the iterable. Will start from 0.
   *   * `iterable`: The iterable on which the operation is being performed.
   * @param {Queue | number} [queueOrConcurrency] If a queue is specified it will be used to schedule the calls to
   * `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
   * implicitly for the same purpose. Defaults to `1`.
   * @param {boolean} [ordered] If true this function will return on the first element in the iterable
   * order for which `iteratee` returned true. If false it will be the first in time.
   * @returns {Promise<number>} A promise that will be resolved with the index of the first found value or rejected if one of the
   * `iteratee` calls throws an exception before finding a value. If no value is found it will return `-1`.
   * @example
   * // example using the default concurrency of 1
   * import { asyncFindIndex, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncFindIndex(array, async (v) => {
   *   // these calls will be performed sequentially
   *   await asyncSleep(Math.random() * 10) // waits a random amount of time between 0ms and 10ms
   *   return v % 2 === 1
   * })
   * console.log(result) // prints 0
   * @example
   * // example using a set concurrency
   * import { asyncFindIndex, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3, 4, 5]
   * const result = await asyncFindIndex(array, async (v) => {
   *   // these calls will be performed in parallel with a maximum of 3
   *   // concurrent calls
   *   await asyncSleep(Math.random() * 10) // waits a random amount of time between 0ms and 10ms
   *   return v % 2 === 1
   * }, 3)
   * console.log(result) // prints 0 or 2, randomly
   * @example
   * // example using infinite concurrency
   * import { asyncFindIndex, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncFindIndex(array, async (v) => {
   *   // these calls will be performed in parallel
   *   await asyncSleep(Math.random() * 10) // waits a random amount of time between 0ms and 10ms
   *   return v % 2 === 1
   * }, Number.POSITIVE_INFINITY)
   * console.log(result) // prints 0 or 2, randomly
   */
  async function asyncFindIndex (iterable, iteratee, queueOrConcurrency = 1, ordered = false) {
    const result = (await asyncFindInternal(iterable, iteratee, queueOrConcurrency, ordered))[0];
    return result
  }

  /**
   * Returns `true` if all elements of an iterable pass a truth test and `false` otherwise.
   *
   * The calls to `iteratee` will be performed in a queue to limit the concurrency of these calls.
   * If any truth test returns `false` the promise is immediately resolved.
   *
   * Whenever a test returns `false`, all the remaining tasks will be cancelled as long
   * as they didn't started already. In case of exception in one of the iteratee calls the promise
   * returned by this function will be rejected with the exception and the remaining pending
   * tasks will also be cancelled. In the very specific case where a test returns `false` and an
   * already started task throws an exception that exception will be plainly ignored.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
   * @param {Function} iteratee A function that will be called with each member of the iterable. It will receive
   * three arguments:
   *   * `value`: The current value to process
   *   * `index`: The index in the iterable. Will start from 0.
   *   * `iterable`: The iterable on which the operation is being performed.
   * @param {Queue | number} [queueOrConcurrency] If a queue is specified it will be used to schedule the calls to
   * `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
   * implicitly for the same purpose. Defaults to `1`.
   * @returns {Promise<boolean>} A promise that will be resolved to `true` if all values pass the truth test and `false`
   * if a least one of them doesn't pass it. That promise will be rejected if one of the truth test throws
   * an exception.
   * @example
   * // example using the default concurrency of 1
   * import { asyncEvery, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   *
   * const result = await asyncEvery(array, async (v) => {
   *   // these calls will be performed sequentially
   *   await asyncSleep(10) // waits 10ms
   *   return v > 0
   * })
   * console.log(result) // prints true
   * // total processing time should be ~ 30ms
   * @example
   * // example using a set concurrency
   * import { asyncEvery, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   *
   * const result = await asyncEvery(array, async (v) => {
   *   // these calls will be performed in parallel with a maximum of 2
   *   // concurrent calls
   *   await asyncSleep(10) // waits 10ms
   *   return v > 0
   * }, 2)
   * console.log(result) // prints true
   * // total processing time should be ~ 20ms
   * @example
   * // example using infinite concurrency
   * import { asyncEvery, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   *
   * const result = await asyncEvery(array, async (v) => {
   *   // these calls will be performed in parallel
   *   await asyncSleep(10) // waits 10ms
   *   return v > 0
   * }, Number.POSITIVE_INFINITY)
   * console.log(result) // prints true
   * // total processing time should be ~ 10ms
   */
  async function asyncEvery (iterable, iteratee, queueOrConcurrency = 1) {
    nanoassert(typeof iteratee === 'function', 'iteratee must be a function');
    iteratee = asyncWrap(iteratee);
    const index = await asyncFindIndex(iterable, async (value, index, iterable) => {
      return !(await iteratee(value, index, iterable))
    }, queueOrConcurrency, false);
    const result = index === -1;
    return result
  }

  /**
   * Fully consumes an iteratable or async iterable an returns an array with all the elements it contained.
   *
   * @param {Iterable | AsyncIterable} iterable An iterator or async iterator.
   * @returns {Promise<any[]>} An array.
   * @example
   * import { asyncIterableToArray, asyncSleep } from 'modern-async'
   *
   * // example async generator
   * async function* asyncGenerator() {
   *   for (let i = 0; i < 3; i += 1) {
   *     await asyncSleep(10)
   *     yield i
   *   }
   * }
   *
   * console.log(await asyncIterableToArray(asyncGenerator()))
   * // prints [0, 1, 2]
   */
  async function asyncIterableToArray (iterable) {
    const it = asyncIterableWrap(iterable);
    const results = [];
    for await (const el of it) {
      results.push(el);
    }
    return results
  }

  /**
   * Produces a an async iterator that will return each value or `iterable` after having processed them through
   * the `iteratee` function.
   *
   * The iterator will perform the calls to `iteratee` in a queue to limit the concurrency of
   * these calls. The iterator will consume values from `iterable` only if slots are available in the
   * queue.
   *
   * If the returned iterator is not fully consumed it will stop consuming new values from `iterable` and scheduling
   * new calls to `iteratee` in the queue, but already scheduled tasks will still be executed.
   *
   * If `iterable` or any of the calls to `iteratee` throws an exception all pending tasks will be cancelled and the
   * returned async iterator will throw that exception.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
   * @param {Function} iteratee A function that will be called with each member of the iterable. It will receive
   * three arguments:
   *   * `value`: The current value to process
   *   * `index`: The index in the iterable. Will start from 0.
   *   * `iterable`: The iterable on which the operation is being performed.
   * @param {Queue | number} [queueOrConcurrency] If a queue is specified it will be used to schedule the calls to
   * `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
   * implicitly for the same purpose. Defaults to `1`.
   * @param {boolean} [ordered] If true the results will be yielded in the same order as in the source
   * iterable, regardless of which calls to iteratee returned first. If false the the results will be yielded as soon
   * as a call to iteratee returned. Defaults to `true`.
   * @yields {any} Each element of `iterable` after processing it through `iteratee`.
   * @example
   * import {asyncGeneratorMap, asyncSleep} from 'modern-async'
   *
   * const iterator = function * () {
   *   for (let i = 0; i < 10000; i += 1) {
   *     yield i
   *   }
   * }
   * const mapIterator = asyncGeneratorMap(iterator(), async (v) => {
   *   await asyncSleep(1000)
   *   return v * 2
   * })
   * for await (const el of mapIterator) {
   *   console.log(el)
   * }
   * // Will print "0", "2", "4", etc... Only one number will be printed per second.
   * // Numbers from `iterator` will be consumed progressively
   */
  async function * asyncGeneratorMap (iterable, iteratee, queueOrConcurrency = 1, ordered = true) {
    nanoassert(typeof iteratee === 'function', 'iteratee must be a function');
    iteratee = asyncWrap(iteratee);
    const it = asyncIterableWrap(iterable);
    const queue = getQueue(queueOrConcurrency);

    /**
     * @ignore
     */
    class CustomCancelledError extends Error {}

    let lastIndexFetched = -1;
    let fetching = false;
    let hasFetchedValue = false;
    let fetchedValue = null;
    let exhausted = false;
    let shouldStop = false;

    let lastIndexHandled = -1;
    const results = [];

    let waitListIndex = 0;
    const waitList = new Map();
    const addToWaitList = (fct) => {
      const identifier = waitListIndex;
      waitListIndex += 1;
      const p = (async () => {
        return [identifier, await reflectAsyncStatus(fct)]
      })();
      nanoassert(!waitList.has(identifier), 'waitList contains identifier');
      waitList.set(identifier, p);
    };
    const raceWaitList = async () => {
      nanoassert(waitList.size >= 1, 'Can not race on empty list');
      const [identifier] = await Promise.race([...waitList.values()]);
      const removed = waitList.delete(identifier);
      nanoassert(removed, 'waitList does not contain identifier');
    };

    let scheduledCount = 0;
    const scheduledList = new Map();
    const schedule = (index, value) => {
      scheduledCount += 1;
      const task = {
        value,
        index,
        cancel: null,
        state: null
      };
      scheduledList.set(index, task);
      addToWaitList(async () => {
        const p = queue.exec(async () => {
          if (task.state === 'cancelled') {
            throw new CustomCancelledError()
          }
          nanoassert(task.state === 'scheduled', 'invalid task state');
          const removed = scheduledList.delete(index);
          nanoassert(removed, 'Couldn\'t find index in scheduledList for removal');

          const snapshot = await reflectAsyncStatus(() => iteratee(value, index, iterable));

          scheduledCount -= 1;
          insertInResults(index, value, snapshot);
          if (snapshot.status === 'rejected') {
            shouldStop = true;
            cancelAllScheduled(ordered ? index : 0);
          }
        });
        nanoassert(task.cancel === null, 'task already has cancel');
        task.cancel = () => {
          nanoassert(task.state === 'scheduled', 'task should be scheduled');
          task.state = 'cancelled';
        };
        nanoassert(task.state === null, 'task should have no state');
        task.state = 'scheduled';
        return p
      });
    };
    const cancelAllScheduled = (fromIndex) => {
      for (const index of [...scheduledList.keys()].filter((el) => el >= fromIndex)) {
        const task = scheduledList.get(index);
        nanoassert(task.cancel, 'task does not have cancel');
        task.cancel();
        const removed = scheduledList.delete(index);
        nanoassert(removed, 'Couldn\'t find index in scheduledList for removal');
      }
    };
    const fetch = () => {
      fetching = true;
      addToWaitList(async () => {
        const snapshot = await reflectAsyncStatus(() => it.next());
        fetching = false;
        if (snapshot.status === 'fulfilled') {
          const { value, done } = snapshot.value;
          if (!done) {
            lastIndexFetched += 1;
            nanoassert(fetchedValue === null, 'fetchedValue should be null');
            fetchedValue = value;
            nanoassert(!hasFetchedValue, 'hasFetchedValue should be false');
            hasFetchedValue = true;
          } else {
            exhausted = true;
          }
        } else {
          exhausted = true;
          lastIndexFetched += 1;
          const index = lastIndexFetched;
          insertInResults(index, undefined, snapshot);
          cancelAllScheduled(ordered ? index : 0);
        }
      });
    };

    const insertInResults = (index, value, snapshot) => {
      if (ordered) {
        nanoassert(index - lastIndexHandled - 1 >= 0, 'invalid index to insert');
        nanoassert(results[index - lastIndexHandled - 1] === undefined, 'already inserted result');
        results[index - lastIndexHandled - 1] = { index, value, snapshot };
      } else {
        results.push({ index, value, snapshot });
      }
    };

    fetch();
    while (true) {
      await raceWaitList();
      while (results.length >= 1 && results[0] !== undefined) {
        const result = results.shift();
        lastIndexHandled += 1;
        if (result.snapshot.status === 'rejected') {
          throw result.snapshot.reason
        } else {
          let yielded = false;
          try {
            yield result.snapshot.value;
            yielded = true;
          } finally {
            if (!yielded) {
              await it.return();
            }
          }
        }
      }
      if (exhausted && lastIndexFetched === lastIndexHandled) {
        return
      }
      if (hasFetchedValue && !shouldStop) {
        schedule(lastIndexFetched, fetchedValue);
        hasFetchedValue = false;
        fetchedValue = null;
      }
      if (!fetching && !exhausted && !shouldStop && scheduledCount < queue.concurrency) {
        fetch();
      }
    }
  }

  /**
   * Produces a an async iterator that will return each value or `iterable` which pass an asynchronous truth test.
   *
   * The iterator will perform the calls to `iteratee` in a queue to limit the concurrency of
   * these calls. The iterator will consume values from `iterable` only if slots are available in the
   * queue.
   *
   * If the returned iterator is not fully consumed it will stop consuming new values from `iterable` and scheduling
   * new calls to `iteratee` in the queue, but already scheduled tasks will still be executed.
   *
   * If `iterable` or any of the calls to `iteratee` throws an exception all pending tasks will be cancelled and the
   * returned async iterator will throw that exception.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
   * @param {Function} iteratee A function that will be called with each member of the iterable. It will receive
   * three arguments:
   *   * `value`: The current value to process
   *   * `index`: The index in the iterable. Will start from 0.
   *   * `iterable`: The iterable on which the operation is being performed.
   * @param {Queue | number} [queueOrConcurrency] If a queue is specified it will be used to schedule the calls to
   * `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
   * implicitly for the same purpose. Defaults to `1`.
   * @param {boolean} [ordered] If true the results will be yielded in the same order as in the source
   * iterable, regardless of which calls to iteratee returned first. If false the the results will be yielded as soon
   * as a call to iteratee returned. Defaults to `true`.
   * @yields {any} Each element of `iterable` for which `iteratee` returned `true`.
   * @example
   * import {asyncGeneratorFilter, asyncSleep} from 'modern-async'
   *
   * const iterator = function * () {
   *   for (let i = 0; i < 10000; i += 1) {
   *     yield i
   *   }
   * }
   * const filterIterator = asyncGeneratorFilter(iterator(), async (v) => {
   *   await asyncSleep(1000)
   *   return v % 3 === 0
   * })
   * for await (const el of filterIterator) {
   *   console.log(el)
   * }
   * // will print "0", "3", "6", etc... Only one number will be printed every 3 seconds.
   */
  async function * asyncGeneratorFilter (iterable, iteratee, queueOrConcurrency = 1, ordered = true) {
    nanoassert(typeof iteratee === 'function', 'iteratee must be a function');
    iteratee = asyncWrap(iteratee);
    for await (const [value, pass] of asyncGeneratorMap(iterable, async (v, i, t) => {
      return [v, await iteratee(v, i, t)]
    }, queueOrConcurrency, ordered)) {
      if (pass) {
        yield value;
      }
    }
  }

  /**
   * Returns an array of all the values in `iterable` which pass an asynchronous truth test.
   *
   * The calls to `iteratee` will be performed in a queue to limit the concurrency of these calls.
   * The results will be in the same order than in `iterable`.
   *
   * If any of the calls to `iteratee` throws an exception the returned promise will be rejected and the remaining
   * pending tasks will be cancelled.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
   * @param {Function} iteratee A function that will be called with each member of `iterable`. It will receive
   * three arguments:
   *   * `value`: The current value to process
   *   * `index`: The index in the iterable. Will start from 0.
   *   * `iterable`: The iterable on which the operation is being performed.
   * @param {Queue | number} [queueOrConcurrency] If a queue is specified it will be used to schedule the calls to
   * `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
   * implicitly for the same purpose. Defaults to `1`.
   * @returns {Promise<any[]>} A promise that will be resolved with an array containing all the values that passed
   * the truth test. This promise will be rejected if any of the `iteratee` calls throws an exception.
   * @example
   * // example using the default concurrency of 1
   * import { asyncFilter, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncFilter(array, async (v) => {
   *   // these calls will be performed sequentially
   *   await asyncSleep(10) // waits 10ms
   *   return v % 2 === 1
   * })
   * console.log(result) // prints [1, 3]
   * // total processing time should be ~ 30ms
   * @example
   * // example using a set concurrency
   * import { asyncFilter, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncFilter(array, async (v) => {
   *   // these calls will be performed in parallel with a maximum of 2
   *   // concurrent calls
   *   await asyncSleep(10) // waits 10ms
   *   return v % 2 === 1
   * }, 2)
   * console.log(result) // prints [1, 3]
   * // total processing time should be ~ 20ms
   * @example
   * // example using infinite concurrency
   * import { asyncFilter, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncFilter(array, async (v) => {
   *   // these calls will be performed in parallel
   *   await asyncSleep(10) // waits 10ms
   *   return v % 2 === 1
   * }, Number.POSITIVE_INFINITY)
   * console.log(result) // prints [1, 3]
   * // total processing time should be ~ 10ms
   */
  async function asyncFilter (iterable, iteratee, queueOrConcurrency = 1) {
    return await asyncIterableToArray(asyncGeneratorFilter(iterable, iteratee, queueOrConcurrency))
  }

  /**
   * Returns the first element of an iterable that passes an asynchronous truth test.
   *
   * The calls to `iteratee` will be performed in a queue to limit the concurrency of these calls.
   *
   * Whenever a result is found, all the remaining tasks will be cancelled as long
   * as they didn't started already. In case of exception in one of the `iteratee` calls the promise
   * returned by this function will be rejected with the exception and the remaining pending
   * tasks will also be cancelled. In the very specific case where a result is found and an
   * already started task throws an exception that exception will be plainly ignored.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
   * @param {Function} iteratee A function that will be called with each member of the iterable. It will receive
   * three arguments:
   *   * `value`: The current value to process
   *   * `index`: The index in the iterable. Will start from 0.
   *   * `iterable`: The iterable on which the operation is being performed.
   * @param {Queue | number} [queueOrConcurrency] If a queue is specified it will be used to schedule the calls to
   * `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
   * implicitly for the same purpose. Defaults to `1`.
   * @param {boolean} [ordered] If true this function will return on the first element in the iterable
   * order for which `iteratee` returned true. If false it will be the first in time.
   * @returns {Promise<any | undefined>} A promise that will be resolved with the first found value or rejected if one of the
   * `iteratee` calls throws an exception before finding a value. If no value is found it will return `undefined`.
   * @example
   * // example using the default concurrency of 1
   * import { asyncFind, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncFind(array, async (v) => {
   *   // these calls will be performed sequentially
   *   await asyncSleep(Math.random() * 10) // waits a random amount of time between 0ms and 10ms
   *   return v % 2 === 1
   * })
   * console.log(result) // prints 1
   * @example
   * // example using a set concurrency
   * import { asyncFind, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3, 4, 5]
   * const result = await asyncFind(array, async (v) => {
   *   // these calls will be performed in parallel with a maximum of 3
   *   // concurrent calls
   *   await asyncSleep(Math.random() * 10) // waits a random amount of time between 0ms and 10ms
   *   return v % 2 === 1
   * }, 3)
   * console.log(result) // prints 1 or 3, randomly
   * @example
   * // example using infinite concurrency
   * import { asyncFind, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncFind(array, async (v) => {
   *   // these calls will be performed in parallel
   *   await asyncSleep(Math.random() * 10) // waits a random amount of time between 0ms and 10ms
   *   return v % 2 === 1
   * }, Number.POSITIVE_INFINITY)
   * console.log(result) // prints 1 or 3, randomly
   */
  async function asyncFind (iterable, iteratee, queueOrConcurrency = 1, ordered = false) {
    const result = (await asyncFindInternal(iterable, iteratee, queueOrConcurrency, ordered))[1];
    return result
  }

  /**
   * Calls a function on each element of iterable.
   *
   * The calls to `iteratee` will be performed in a queue to limit the concurrency of these calls.
   *
   * If any of the calls to iteratee throws an exception the returned promise will be rejected and the remaining
   * pending tasks will be cancelled.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
   * @param {Function} iteratee A function that will be called with each member of the iterable. It will receive
   * three arguments:
   *   * `value`: The current value to process
   *   * `index`: The index in the iterable. Will start from 0.
   *   * `iterable`: The iterable on which the operation is being performed.
   * @param {Queue | number} [queueOrConcurrency] If a queue is specified it will be used to schedule the calls to
   * `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
   * implicitly for the same purpose. Defaults to `1`.
   * @returns {Promise} A promise that will be resolved when all the calls to `iteratee` have been done.
   * This promise will be rejected if any call to `iteratee` throws an exception.
   * @example
   * // example using the default concurrency of 1
   * import { asyncForEach, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * await asyncForEach(array, async (v) => {
   *   // these calls will be performed sequentially
   *   await asyncSleep(Math.random() * 10) // waits a random amount of time between 0ms and 10ms
   *   console.log(v)
   * })
   * // prints 1, 2 and 3 in that exact order
   * @example
   * // example using a set concurrency
   * import { asyncForEach, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * await asyncForEach(array, async (v) => {
   *   // these calls will be performed in parallel with a maximum of 2
   *   // concurrent calls
   *   await asyncSleep(Math.random() * 10) // waits a random amount of time between 0ms and 10ms
   *   console.log(v)
   * }, 2)
   * // prints 1, 2 and 3 in a random order (it will always print 1 or 2 before printing 3 due to
   * // the concurrency limit and the internal scheduling order)
   * @example
   * // example using infinite concurrency
   * import { asyncForEach, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * await asyncForEach(array, async (v) => {
   *   // these calls will be performed in parallel
   *   await asyncSleep(Math.random() * 10) // waits a random amount of time between 0ms and 10ms
   *   console.log(v)
   * }, Number.POSITIVE_INFINITY)
   * // prints 1, 2 and 3 in a random order
   */
  async function asyncForEach (iterable, iteratee, queueOrConcurrency = 1) {
    // eslint-disable-next-line no-unused-vars
    for await (const _el of asyncGeneratorMap(iterable, iteratee, queueOrConcurrency)) {
      // do nothing
    }
  }

  /**
   * Produces a new collection of values by mapping each value in `iterable` through the `iteratee` function.
   *
   * The calls to `iteratee` will be performed in a queue to limit the concurrency of these calls.
   *
   * If any of the calls to iteratee throws an exception the returned promise will be rejected and the remaining
   * pending tasks will be cancelled.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
   * @param {Function} iteratee A function that will be called with each member of the iterable. It will receive
   * three arguments:
   *   * `value`: The current value to process
   *   * `index`: The index in the iterable. Will start from 0.
   *   * `iterable`: The iterable on which the operation is being performed.
   * @param {Queue | number} [queueOrConcurrency] If a queue is specified it will be used to schedule the calls to
   * `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
   * implicitly for the same purpose. Defaults to `1`.
   * @returns {Promise<any[]>} A promise that will be resolved with an array containing all the mapped value,
   * or will be rejected if any of the calls to `iteratee` throws an exception.
   * @example
   * // example using the default concurrency of 1
   * import { asyncMap, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncMap(array, async (v) => {
   *   // these calls will be performed sequentially
   *   await asyncSleep(10) // waits 10ms
   *   return v * 2
   * }, 2)
   * console.log(result) // prints [2, 4, 6]
   * // total processing time should be ~ 30ms
   * @example
   * // example using a set concurrency
   * import { asyncMap, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncMap(array, async (v) => {
   *   // these calls will be performed in parallel with a maximum of 2
   *   // concurrent calls
   *   await asyncSleep(10) // waits 10ms
   *   return v * 2
   * }, 2)
   * console.log(result) // prints [2, 4, 6]
   * // total processing time should be ~ 20ms
   * @example
   * // example using infinite concurrency
   * import { asyncMap, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncMap(array, async (v) => {
   *   // these calls will be performed in parallel
   *   await asyncSleep(10) // waits 10ms
   *   return v * 2
   * }, Number.POSITIVE_INFINITY)
   * console.log(result) // prints [2, 4, 6]
   * // total processing time should be ~ 10ms
   */
  async function asyncMap (iterable, iteratee, queueOrConcurrency = 1) {
    return await asyncIterableToArray(asyncGeneratorMap(iterable, iteratee, queueOrConcurrency))
  }

  /**
   * Performs a reduce operation as defined in the `Array.reduce()` method but using an asynchronous
   * function as reducer. The reducer will be called sequentially.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
   * @param {Function} reducer The reducer function. It will be called with four arguments:
   *   * `accumulator`: The last calculated value (or the first value of the iterable if no initial value is provided)
   *   * `value`: The current value
   *   * `index`: The current index in the iterable. Will start from 0 if no initial value is provided, 1 otherwise.
   *   * `iterable`: The iterable on which the reduce operation is performed.
   * @param {any} [initial] The initial value that will be used as accumulator in the first call to
   *   `reducer`. If omitted the first element of `iterable` will be used as accumulator and `reducer`
   *   will only be called from from the second element of the list (as defined in the `Array.reduce()`
   *   function).
   * @returns {Promise} A promise that will be resolved with the result of the reduce operation,
   *   or rejected if any of the calls to `reducer` throws an exception.
   * @example
   * import { asyncReduce, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncReduce(array, async (v, p) => {
   *   // these calls will be performed sequentially
   *   await asyncSleep(10) // waits 10ms
   *   return v + p
   * })
   * console.log(result) // prints 6
   * // total processing time should be ~ 20ms
   */
  async function asyncReduce (iterable, reducer, initial = undefined) {
    nanoassert(typeof reducer === 'function', 'iteratee must be a function');
    reducer = asyncWrap(reducer);
    if (initial !== undefined) {
      let current = initial;
      let i = 0;
      for await (const el of iterable) {
        current = await reducer(current, el, i, iterable);
        i += 1;
      }
      return current
    } else {
      let i = 0;
      let current;
      for await (const el of iterable) {
        if (i === 0) {
          current = el;
        } else {
          current = await reducer(current, el, i, iterable);
        }
        i += 1;
      }
      if (i === 0) {
        throw new TypeError('Reduce of empty array with no initial value')
      }
      return current
    }
  }

  /**
   * Performs a reduce operation as defined in the `Array.reduceRight()` method but using an asynchronous
   * function as reducer. The reducer will be called sequentially.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable object.
   * @param {Function} reducer The reducer function. It will be called with four arguments:
   *   * `accumulator`: The last calculated value (or the first value of the iterable if no initial value is provided)
   *   * `value`: The current value
   *   * `index`: The current index in the iterable. Will start from the last index if no initial value is provided,
   *     the last index minus 1 otherwise.
   *   * `iterable`: The iterable on which the reduce operation is performed.
   * @param {any} [initial] The initial value that will be used as accumulator in the first call to
   *   reducer. If omitted the first element of `iterable` will be used as accumulator and `reducer`
   *   will only be called from from the second element of the list (as defined in the `Array.reduce()`
   *   function).
   * @returns {Promise} A promise that will be resolved with the result of the reduce operation,
   *   or rejected if any of the calls to `reducer` throws an exception.
   * @example
   * import { asyncReduceRight, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   * const result = await asyncReduceRight(array, async (v, p) => {
   *   // these calls will be performed sequentially
   *   await asyncSleep(10) // waits 10ms
   *   return v + p
   * })
   * console.log(result) // prints 6
   * // total processing time should be ~ 20ms
   */
  async function asyncReduceRight (iterable, reducer, initial = undefined) {
    nanoassert(typeof reducer === 'function', 'iteratee must be a function');
    reducer = asyncWrap(reducer);
    const arr = [];
    for await (const el of iterable) {
      arr.push(el);
    }
    arr.reverse();
    return asyncReduce(arr, async (accumulator, value, index, iterable) => {
      return reducer(accumulator, value, arr.length - 1 - index, iterable)
    }, initial)
  }

  /**
   * Waits a given amount of time. This function returns both a promise and cancel function in
   * order to cancel the wait time if necessary. If cancelled, the promise will be rejected
   * with a `CancelledError`.
   *
   * This function uses `setTimeout()` internally and has the same behavior, notably that it could resolve
   * after the asked time (depending on other tasks running in the event loop) or a few milliseconds before.
   *
   * @param {number} amount An amount of time in milliseconds
   * @returns {Array} A tuple of two objects:
   *   * `promise`: The promise
   *   * `cancel`: The cancel function. It will return a boolean that will be `true` if the promise was effectively cancelled,
   *     `false` otherwise.
   * @example
   * import { asyncSleepCancellable } from 'modern-async'
   *
   * const [promise, cancel] = asyncSleepCancellable(100) // schedule to resolve the promise after 100ms
   *
   * cancel()
   *
   * try {
   *   await promise
   * } catch (e) {
   *   console.log(e.name) // prints CancelledError
   * }
   */
  function asyncSleepCancellable (amount) {
    nanoassert(typeof amount === 'number', 'amount must be a number');
    const deferred = new Deferred();
    const id = setTimeout(deferred.resolve, amount);
    let terminated = false;
    return [deferred.promise.finally(() => {
      terminated = true;
    }), () => {
      if (terminated) {
        return false
      } else {
        terminated = true;
        deferred.reject(new CancelledError());
        clearTimeout(id);
        return true
      }
    }]
  }

  /**
   * A class implementing a scheduler.
   *
   * It fills the same purpose than setInterval() but its behavior is more adapted to asynchronous
   * tasks. Notably it can limit the concurrency of asynchronous tasks running in parallel.
   *
   * @example
   * import { Scheduler, asyncSleep } from 'modern-async'
   *
   * let i = 0
   * const scheduler = new Scheduler(async () => {
   *   const taskNbr = i
   *   i += 1
   *   console.log(`Starting task ${taskNbr}`)
   *   await asyncSleep(10) // waits 10ms
   *   console.log(`Ending task ${taskNbr}`)
   * }, 100) // a scheduler that triggers every 100ms
   * // the default configuration uses a maximum concurrency of 1 and doesn't allow pending
   * // tasks, which mean that if a task takes more time to complete than the delay it will be skipped
   *
   * scheduler.start() // starts the scheduler
   *
   * await asyncSleep(1000) // waits 1s, during that time the task should trigger ~ 9 times
   *
   * scheduler.stop() // stops the scheduler
   * console.log('Scheduler stopped')
   * // no "Starting task" console message may appear from here but you could still see a
   * // "Stopping task" as there could have a task that started before we stopped the
   * // scheduler
   */
  class Scheduler {
    /**
     * Constructs a Scheduler.
     *
     * @param {Function} fct The asynchronous function to call when the scheduler is triggered.
     * @param {number} delay The delay between two triggering of the scheduler, in ms.
     * @param {{startImmediate: boolean, concurrency: number, maxPending: number}} [options] An object that can contain additional options:
     *
     *   * `startImmediate`: If true a new task will be triggered as soon as the start() method is called.
     *     Defaults to ´false`.
     *   * `concurrency`: The maximum number of concurrent tasks. See the `concurrency` attribute. Defaults to 1.
     *   * `maxPending`: The maximum number of pending tasks. See the `maxPending` attribute. Defaults to 0.
     */
    constructor (fct, delay, options = null) {
      options = options || {};
      this._asyncFct = asyncWrap(fct);
      this._delay = delay;
      nanoassert(typeof this._delay === 'number', 'delay must be a number');
      nanoassert(this._delay >= 0, 'delay must be greater or equal than 0');
      this._startImmediate = options.startImmediate || false;
      nanoassert(typeof this._startImmediate === 'boolean',
        'startImmediate must be a boolean');
      this._maxPending = options.maxPending || 0;
      nanoassert(Number.isInteger(this._maxPending) || this._maxPending === Number.POSITIVE_INFINITY,
        'maxPending must be an integer or positive infinity');
      nanoassert(this._maxPending >= 0, 'maxPending must be greater or equal than 0');
      this._queue = new Queue(options.concurrency || 1);
      this._started = false;
      this._initialTime = null;
      this._nbrTriggering = null;
      this._cancelSleep = null;
    }

    /**
     * (Read-only) The delay between two triggering of the scheduler, in milliseconds.
     *
     * @member {number}
     * @returns {number} ignore
     */
    get delay () {
      return this._delay
    }

    /**
     * (Read-only) Whether or not a triggering of the task should occur immediately when calling `start()` or not.
     *
     * Defaults to false.
     *
     * @member {boolean}
     * @returns {boolean} ignore
     */
    get startImmediate () {
      return this._startImmediate
    }

    /**
     * (Read-only) The maximum number of asynchronous tasks that can run in parallel.
     *
     * This parameter only matters in the event where some tasks may take more time to execute
     * than the delay. If the concurrency allows it the new task will be run concurrently. If not
     * it may be scheduled to be executed depending on the configuration of the `maxPending` parameter.
     *
     * Defaults to 1.
     *
     * @member {number}
     * @returns {number} ignore
     */
    get concurrency () {
      return this._queue.concurrency
    }

    /**
     * (Read-only) The maximum number of tasks that can be pending.
     *
     * In the event where one of the tasks triggered by the scheduler takes more time to execute than
     * the delay the next task may or may not be run concurrently depending on the configuration of
     * the `concurrency` parameter. If the maximum concurrency was already reached the new task can
     * be scheduled to be executed as soon as the previous task finished.
     *
     * This parameter indicates the maximum amount of tasks that can be pending at any time. If a
     * task should be scheduled and the maximum amount of pending tasks is already reached
     * that new task will be skipped.
     *
     * This behavior helps to prevent cases that would lead to a infinite amount of tasks to be
     * pending. This could happen in extreme cases where the tasks would take systematically more
     * time to execute than the delay.
     *
     * Defaults to 0.
     *
     * @member {number}
     * @returns {number} ignore
     */
    get maxPending () {
      return this._maxPending
    }

    /**
     * (Read-only) Whether or not the scheduler is actually started.
     *
     * @member {boolean}
     * @returns {boolean} ignore
     */
    get started () {
      return this._started
    }

    /**
     * Starts the scheduler.
     *
     * Calling this method can trigger a task immediately depending on the configuration
     * of the `startImmediate` parameter.
     *
     * If this method is called while the scheduler is already started it will have no effect.
     */
    start () {
      if (this.started) {
        return
      }
      nanoassert(this._queue.pending === 0);
      this._started = true;

      this._initialTime = new Date().getTime();
      this._nbrTriggering = 0;

      if (this.startImmediate) {
        this._triggerTask();
      }

      this._scheduleSleep();
    }

    /**
     * Stops the scheduler.
     *
     * If, for any reason, there were pending tasks in the scheduler they will be cancelled. On the other
     * hand if they are still one or more tasks that are running they will continue to run until they
     * terminate.
     *
     * This method is safe to call in a task if necessary.
     *
     * If this method is called while the scheduler is already stopped it will have no effect.
     */
    stop () {
      if (!this.started) {
        return
      }
      nanoassert(!!this._cancelSleep);
      this._cancelSleep();
      this._cancelSleep = null;
      this._queue.cancelAllPending();
      nanoassert(this._queue.pending === 0);
      this._started = false;
      this._initialTime = null;
      this._nbrTriggering = null;
    }

    /**
     * @ignore
     */
    _scheduleSleep () {
      this._nbrTriggering += 1;
      const nextTime = this._initialTime + (this.delay * this._nbrTriggering);
      const currentTime = new Date().getTime();
      const [promise, cancel] = asyncSleepCancellable(nextTime - currentTime);
      this._cancelSleep = cancel;
      promise.then(() => {
        this._triggerTask();
        this._scheduleSleep();
      }, () => {
        // ignore cancelled asyncSleep
      });
    }

    /**
     * @ignore
     */
    _triggerTask () {
      const reachedMaxConcurrency = this._queue.running === this._queue.concurrency;
      const forecastPending = reachedMaxConcurrency ? this._queue.pending + 1 : 0;
      if (forecastPending <= this.maxPending) {
        this._queue.exec(this._asyncFct).catch(exceptionHandler);
      }
    }
  }

  const exceptionHandler = (e) => {
    if (e instanceof CancelledError) ; else {
      throw e
    }
  };

  /**
   * Waits a given amount of time.
   *
   * This function uses `setTimeout()` internally and has the same behavior, notably that it could resolve
   * after the asked time (depending on other tasks running in the event loop) or a few milliseconds before.
   *
   * @param {number} amount An amount of time in milliseconds
   * @returns {Promise<void>} A promise that will be resolved after the given amount of time has passed.
   * @example
   * import { asyncSleep } from 'modern-async'
   *
   * await asyncSleep(100) // will wait 100ms
   * @example
   * // another example that doesn't block on the asyncSleep call
   * // it's functionally identical to using setTimout but with a promise syntax
   * import { asyncSleep } from 'modern-async'
   *
   * asyncSleep(10).then(() => {
   *   console.log('hello')
   * })
   * // will print 'hello' after 10ms
   */
  async function asyncSleep (amount) {
    return asyncSleepCancellable(amount)[0]
  }

  /**
   * Waits a given amount of time.
   *
   * This function returns both a promise and cancel function in order to cancel the
   * wait time if necessary. If cancelled, the promise will be rejected with a `CancelledError`.
   *
   * This function is similar to `asyncSleep()` except it ensures that the amount of time measured
   * using the `Date` object is always greater than or equal the asked amount of time.
   *
   * This function can imply additional delay that can be bad for performances. As such it is
   * recommended to only use it in unit tests or very specific cases. Most applications should
   * be adapted to work with the usual `setTimout()` inconsistencies even if it can trigger some
   * milliseconds before the asked delay.
   *
   * @param {number} amount An amount of time in milliseconds
   * @returns {Array} A tuple of two objects:
   *   * `promise`: The promise
   *   * `cancel`: The cancel function. It will return a boolean that will be `true` if the promise was effectively cancelled,
   *     `false` otherwise.
   * @example
   * import { asyncSleepPreciseCancellable } from 'modern-async'
   *
   * const [promise, cancel] = asyncSleepPreciseCancellable(100) // schedule to resolve the promise after 100ms
   *
   * cancel()
   *
   * try {
   *   await promise
   * } catch (e) {
   *   console.log(e.name) // prints CancelledError
   * }
   */
  function asyncSleepPreciseCancellable (amount) {
    return _innerWaitPreciseCancellable(amount, (ellasped, amount) => {
      return ellasped >= amount
    })
  }

  /**
   * @ignore
   * @param {*} amount ignored
   * @param {*} checkPassed ignored
   * @returns {*} ignored
   */
  function _innerWaitPreciseCancellable (amount, checkPassed) {
    nanoassert(typeof amount === 'number', 'amount must be a number');
    const start = new Date().getTime();
    const [p, cancel] = asyncSleepCancellable(amount);
    let lastCancel = cancel;
    const deferred = new Deferred();
    const reject = (e) => {
      deferred.reject(e);
    };
    const resolve = () => {
      const now = new Date().getTime();
      const ellasped = now - start;
      if (checkPassed(ellasped, amount)) {
        deferred.resolve();
      } else {
        const [np, ncancel] = asyncSleepCancellable(amount - ellasped);
        lastCancel = ncancel;
        np.then(resolve, reject);
      }
    };
    p.then(resolve, reject);
    return [deferred.promise, () => {
      return lastCancel()
    }]
  }

  /**
   * Waits a given amount of time.
   *
   * This function is similar to `asyncSleep()` except it ensures that the amount of time measured
   * using the `Date` object is always greater than or equal the asked amount of time.
   *
   * This function can imply additional delay that can be bad for performances. As such it is
   * recommended to only use it in unit tests or very specific cases. Most applications should
   * be adapted to work with the usual `setTimout()` inconsistencies even if it can trigger some
   * milliseconds before the asked delay.
   *
   * @param {number} amount An amount of time in milliseconds
   * @returns {Promise<void>} A promise that will be resolved after the given amount of time has passed.
   * @example
   * import { asyncSleepPrecise } from 'modern-async'
   *
   * await asyncSleepPrecise(100) // will wait 100ms
   */
  async function asyncSleepPrecise (amount) {
    return asyncSleepPreciseCancellable(amount)[0]
  }

  /**
   * Returns `true` if at least one element of an iterable pass a truth test and `false` otherwise.
   *
   * The calls to `iteratee` will be performed in a queue to limit the concurrency of these calls. If any
   * truth test returns `true` the promise is immediately resolved.
   *
   * Whenever a test returns `true`, all the remaining tasks will be cancelled as long
   * as they didn't started already. In case of exception in one of the `iteratee` calls the promise
   * returned by this function will be rejected with the exception and the remaining pending
   * tasks will also be cancelled. In the very specific case where a test returns `true` and an
   * already started task throws an exception that exception will be plainly ignored.
   *
   * @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
   * @param {Function} iteratee A function that will be called with each member of the iterable. It will receive
   * three arguments:
   *   * `value`: The current value to process
   *   * `index`: The index in the iterable. Will start from 0.
   *   * `iterable`: The iterable on which the operation is being performed.
   * @param {Queue | number} [queueOrConcurrency] If a queue is specified it will be used to schedule the calls to
   * `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
   * implicitly for the same purpose. Defaults to `1`.
   * @returns {Promise<boolean>} A promise that will be resolved to `true` if at least one value pass the truth test and `false`
   * if none of them do. That promise will be rejected if one of the truth test throws an exception.
   * @example
   * // example using the default concurrency of 1
   * import { asyncSome, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   *
   * const result = await asyncSome(array, async (v) => {
   *   // these calls will be performed sequentially
   *   await asyncSleep(10) // waits 10ms
   *   return v % 2 === 0
   * })
   * console.log(result) // prints true
   * // total processing time should be ~ 30ms
   * @example
   * // example using a set concurrency
   * import { asyncSome, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   *
   * const result = await asyncSome(array, async (v) => {
   *   // these calls will be performed in parallel with a maximum of 2
   *   // concurrent calls
   *   await asyncSleep(10) // waits 10ms
   *   return v % 2 === 0
   * }, 2)
   * console.log(result) // prints true
   * // total processing time should be ~ 20ms
   * @example
   * // example using infinite concurrency
   * import { asyncSome, asyncSleep } from 'modern-async'
   *
   * const array = [1, 2, 3]
   *
   * const result = await asyncSome(array, async (v) => {
   *   // these calls will be performed in parallel
   *   await asyncSleep(10) // waits 10ms
   *   return v % 2 === 0
   * }, Number.POSITIVE_INFINITY)
   * console.log(result) // prints true
   * // total processing time should be ~ 10ms
   */
  async function asyncSome (iterable, iteratee, queueOrConcurrency = 1) {
    const index = await asyncFindIndex(iterable, iteratee, queueOrConcurrency, false);
    const result = index !== -1;
    return result
  }

  /**
   * An error type which is used when an asynchronous operation takes too much time to perform.
   */
  class TimeoutError extends Error {
    /**
     * Constructs a new instance.
     *
     * @param {string} message The error message
     */
    constructor (message) {
      super(message);
      this.name = this.constructor.name;
    }
  }

  /**
   * Wraps a call to an asynchronous function to add a timer on it. If the delay is exceeded
   * the returned promise will be rejected with a `TimeoutError`.
   *
   * This function uses `setTimeout()` internally and has the same behavior, notably that it could reject
   * after the asked time (depending on other tasks running in the event loop) or a few milliseconds before.
   *
   * @param {Function} fct An asynchronous function that will be called immediately without arguments.
   * @param {number} amount An amount of time in milliseconds
   * @returns {Promise} A promise that will be resolved or rejected according to the result of the call
   * to `fct`. If `amount` milliseconds pass before the call to `fct` returns or rejects, this promise will
   * be rejected with a `TimeoutError`.
   * @example
   * import { asyncTimeout, asyncSleep } from 'modern-async'
   *
   * // the following statement will perform successfully because
   * // the function will return before the delay
   * await asyncTimeout(async () => {
   *   await asyncSleep(10)
   * }, 100)
   *
   * try {
   *   // the following statement will throw after 10ms
   *   await asyncTimeout(async () => {
   *     await asyncSleep(100)
   *   }, 10)
   * } catch (e) {
   *   console.log(e.name) // prints TimeoutError
   * }
   */
  async function asyncTimeout (fct, amount) {
    const asyncFct = asyncWrap(fct);

    const [timoutPromise, cancelTimeout] = asyncSleepCancellable(amount);

    const basePromise = asyncFct();

    const deferred = new Deferred();

    basePromise.then(deferred.resolve, deferred.reject);

    timoutPromise.then(() => {
      deferred.reject(new TimeoutError());
    }, () => {
      // ignore CancelledError
    });

    return deferred.promise.finally(cancelTimeout)
  }

  /**
   * Wraps a call to an asynchronous function to add a timer on it. If the delay is exceeded
   * the returned promise will be rejected with a `TimeoutError`.
   *
   * This function is similar to `asyncTimeout()` except it ensures that the amount of time measured
   * using the `Date` object is always greater than or equal the asked amount of time.
   *
   * This function can imply additional delay that can be bad for performances. As such it is
   * recommended to only use it in unit tests or very specific cases. Most applications should
   * be adapted to work with the usual `setTimout()` inconsistencies even if it can trigger some
   * milliseconds before the asked delay.
   *
   * @param {Function} fct An asynchronous function that will be called immediately without arguments.
   * @param {number} amount An amount of time in milliseconds
   * @returns {Promise} A promise that will be resolved or rejected according to the result of the call
   * to `fct`. If `amount` milliseconds pass before the call to `fct` returns or rejects, this promise will
   * be rejected with a `TimeoutError`.
   * @example
   * import { asyncTimeoutPrecise, asyncSleep } from 'modern-async'
   *
   * // the following statement will perform successfully because
   * // the function will return before the delay
   * await asyncTimeoutPrecise(async () => {
   *   await asyncSleep(10)
   * }, 100)
   *
   * try {
   *   // the following statement will throw after 10ms
   *   await asyncTimeoutPrecise(async () => {
   *     await asyncSleep(100)
   *   }, 10)
   * } catch (e) {
   *   console.log(e.name) // prints TimeoutError
   * }
   */
  async function asyncTimeoutPrecise (fct, amount) {
    const asyncFct = asyncWrap(fct);

    const [timoutPromise, cancelTimeout] = asyncSleepPreciseCancellable(amount);

    const basePromise = asyncFct();

    const deferred = new Deferred();

    basePromise.then(deferred.resolve, deferred.reject);

    timoutPromise.then(() => {
      deferred.reject(new TimeoutError());
    }, () => {
      // ignore CancelledError
    });

    return deferred.promise.finally(cancelTimeout)
  }

  exports.CancelledError = CancelledError;
  exports.Deferred = Deferred;
  exports.Delayer = Delayer;
  exports.Queue = Queue;
  exports.Scheduler = Scheduler;
  exports.TimeoutError = TimeoutError;
  exports.asyncDelay = asyncDelay;
  exports.asyncDelayCancellable = asyncDelayCancellable;
  exports.asyncEvery = asyncEvery;
  exports.asyncFilter = asyncFilter;
  exports.asyncFind = asyncFind;
  exports.asyncFindIndex = asyncFindIndex;
  exports.asyncForEach = asyncForEach;
  exports.asyncGeneratorFilter = asyncGeneratorFilter;
  exports.asyncGeneratorMap = asyncGeneratorMap;
  exports.asyncIterableToArray = asyncIterableToArray;
  exports.asyncIterableWrap = asyncIterableWrap;
  exports.asyncMap = asyncMap;
  exports.asyncReduce = asyncReduce;
  exports.asyncReduceRight = asyncReduceRight;
  exports.asyncRoot = asyncRoot;
  exports.asyncSleep = asyncSleep;
  exports.asyncSleepCancellable = asyncSleepCancellable;
  exports.asyncSleepPrecise = asyncSleepPrecise;
  exports.asyncSleepPreciseCancellable = asyncSleepPreciseCancellable;
  exports.asyncSome = asyncSome;
  exports.asyncTimeout = asyncTimeout;
  exports.asyncTimeoutPrecise = asyncTimeoutPrecise;
  exports.asyncWrap = asyncWrap;
  exports.queueMicrotask = queueMicrotask;
  exports.reflectAsyncStatus = reflectAsyncStatus;

  Object.defineProperty(exports, '__esModule', { value: true });

}));