@ 261106.15
current
/**
 * @license
 * Copyright 2023 The Emscripten Authors
 * SPDX-License-Identifier: MIT
 */

addToLibrary({
  $promiseMap: 'new HandleAllocator();',

  $getPromise: (id) => promiseMap.get(id).promise,

  $makePromise: () => {
    var promiseInfo = {};
    promiseInfo.promise = new Promise((resolve, reject) => {
      promiseInfo.reject = reject;
      promiseInfo.resolve = resolve;
    });
    promiseInfo.id = promiseMap.allocate(promiseInfo);
#if RUNTIME_DEBUG
    dbg(`makePromise: ${promiseInfo.id}`);
#endif
    return promiseInfo;
  },

  $addPromise: (promise) => promiseMap.allocate({promise}),

  $idsToPromises: (idBuf, size) => {
    var promises = [];
    for (var i = 0; i < size; i++) {
      var id = {{{ makeGetValue('idBuf', `i*${POINTER_SIZE}`, 'i32') }}};
      promises[i] = getPromise(id);
    }
    return promises;
  },

  emscripten_promise_create: () => makePromise().id,

  emscripten_promise_destroy: (id) => {
#if RUNTIME_DEBUG
    dbg(`emscripten_promise_destroy: ${id}`);
#endif
    promiseMap.free(id);
  },

  emscripten_promise_resolve: (id, result, value) => {
#if RUNTIME_DEBUG
    dbg(`emscripten_promise_resolve: ${id}`);
#endif
    var info = promiseMap.get(id);
    switch (result) {
      case EM_PROMISE_FULFILL:
        info.resolve(value);
        return;
      case EM_PROMISE_MATCH:
        info.resolve(getPromise(value));
        return;
      case EM_PROMISE_MATCH_RELEASE:
        info.resolve(getPromise(value));
        _emscripten_promise_destroy(value);
        return;
      case EM_PROMISE_REJECT:
        info.reject(value);
        return;
    }
#if ASSERTIONS
    abort(`unexpected promise callback result ${result}`);
#endif
  },

  $makePromiseCallback: (callback, userData) => {
    if (!callback) return;
    return (value) => {
#if RUNTIME_DEBUG
      dbg(`emscripten promise callback: ${value}`);
#endif
      {{{ runtimeKeepalivePop() }}};
      var stack = stackSave();
      // Allocate space for the result value and initialize it to NULL.
      var resultPtr = stackAlloc(POINTER_SIZE);
      {{{ makeSetValue('resultPtr', 0, '0', '*') }}};
      try {
        var result =
            {{{ makeDynCall('ippp', 'callback') }}}(resultPtr, userData, value);
        var resultVal = {{{ makeGetValue('resultPtr', 0, '*') }}};
      } catch (e) {
        // If the thrown value is potentially a valid pointer, use it as the
        // rejection reason. Otherwise use a null pointer as the reason. If we
        // allow arbitrary objects to be thrown here, we will get a TypeError in
        // MEMORY64 mode when they are later converted to void* rejection
        // values.
#if MEMORY64
        if (typeof e !== 'bigint') {
          throw 0n;
        }
#else
        if (typeof e !== 'number') {
          throw 0;
        }
#endif
        throw e;
      } finally {
        // Thrown errors will reject the promise, but at least we will restore
        // the stack first.
        stackRestore(stack);
      }
      switch (result) {
        case EM_PROMISE_FULFILL:
          return resultVal;
        case EM_PROMISE_MATCH:
          return getPromise(resultVal);
        case EM_PROMISE_MATCH_RELEASE:
          var ret = getPromise(resultVal);
          _emscripten_promise_destroy(resultVal);
          return ret;
        case EM_PROMISE_REJECT:
          throw resultVal;
      }
#if ASSERTIONS
      abort(`unexpected promise callback result ${result}`);
#endif
    };
  },

  emscripten_promise_then: (id, onFulfilled, onRejected, userData) => {
#if RUNTIME_DEBUG
    dbg(`emscripten_promise_then: ${id}`);
#endif
    {{{ runtimeKeepalivePush() }}};
    var promise = getPromise(id);
    var chainedPromise = promise.then(makePromiseCallback(onFulfilled, userData),
                                      makePromiseCallback(onRejected, userData));
    var newId = addPromise(chainedPromise);
#if RUNTIME_DEBUG
    dbg(`emscripten_promise_then: -> ${newId}`);
#endif
    return newId;
  },

  emscripten_promise_all: (idBuf, resultBuf, size) => {
    var promises = idsToPromises(idBuf, size);
#if RUNTIME_DEBUG
    dbg(`emscripten_promise_all: ${promises}`);
#endif
    var id = addPromise(Promise.all(promises).then((results) => {
      if (resultBuf) {
        for (var i = 0; i < size; i++) {
          var result = results[i];
          {{{ makeSetValue('resultBuf', `i*${POINTER_SIZE}`, 'result', '*') }}};
        }
      }
      return resultBuf;
    }));
#if RUNTIME_DEBUG
    dbg(`create: ${id}`);
#endif
    return id;
  },

  $setPromiseResult__internal: true,
  $setPromiseResult: (ptr, fulfill, value) => {
#if ASSERTIONS
    assert(typeof value === 'undefined' || typeof value === 'number', `native promises can only handle numeric results (${value} ${typeof value})`);
#endif
    var result = fulfill ? EM_PROMISE_FULFILL : EM_PROMISE_REJECT
    {{{ makeSetValue('ptr', C_STRUCTS.em_settled_result_t.result, 'result', 'i32') }}};
    {{{ makeSetValue('ptr', C_STRUCTS.em_settled_result_t.value, 'value', '*') }}};
  },

  emscripten_promise_all_settled: (idBuf, resultBuf, size) => {
    var promises = idsToPromises(idBuf, size);
#if RUNTIME_DEBUG
    dbg(`emscripten_promise_all_settled: ${promises}`);
#endif
    var id = addPromise(Promise.allSettled(promises).then((results) => {
      if (resultBuf) {
        var offset = resultBuf;
        for (var i = 0; i < size; i++, offset += {{{ C_STRUCTS.em_settled_result_t.__size__ }}}) {
          if (results[i].status === 'fulfilled') {
            setPromiseResult(offset, true, results[i].value);
          } else {
            setPromiseResult(offset, false, results[i].reason);
          }
        }
      }
      return resultBuf;
    }));
#if RUNTIME_DEBUG
    dbg(`create: ${id}`);
#endif
    return id;
  },

  emscripten_promise_any: (idBuf, errorBuf, size) => {
    var promises = idsToPromises(idBuf, size);
#if RUNTIME_DEBUG
    dbg(`emscripten_promise_any: ${promises}`);
#endif
#if ASSERTIONS
    assert(typeof Promise.any !== 'undefined', 'Promise.any does not exist');
#endif
    var id = addPromise(Promise.any(promises).catch((err) => {
      if (errorBuf) {
        for (var i = 0; i < size; i++) {
          {{{ makeSetValue('errorBuf', `i*${POINTER_SIZE}`, 'err.errors[i]', '*') }}};
        }
      }
      throw errorBuf;
    }));
#if RUNTIME_DEBUG
    dbg(`create: ${id}`);
#endif
    return id;
  },

  emscripten_promise_race: (idBuf, size) => {
    var promises = idsToPromises(idBuf, size);
#if RUNTIME_DEBUG
    dbg(`emscripten_promise_race: ${promises}`);
#endif
    var id = addPromise(Promise.race(promises));
#if RUNTIME_DEBUG
    dbg(`create: ${id}`);
#endif
    return id;
  },

#if ASYNCIFY
  emscripten_promise_await__async: 'auto',
  emscripten_promise_await: (returnValuePtr, id) => {
#if RUNTIME_DEBUG
    dbg(`emscripten_promise_await: ${id}`);
#endif
    return getPromise(id).then(
      value => setPromiseResult(returnValuePtr, true, value),
      error => setPromiseResult(returnValuePtr, false, error)
    );
  },

  emscripten_promise_await_unchecked__async: 'auto',
  emscripten_promise_await_unchecked: (id) => {
#if RUNTIME_DEBUG
    dbg(`emscripten_promise_await_unchecked: ${id}`);
#endif
    return getPromise(id);
  },
#else
  emscripten_promise_await: (returnValuePtr, id) => {
    abort('emscripten_promise_await is only available with ASYNCIFY');
  },
  emscripten_promise_await_unchecked: (id) => {
    abort('emscripten_promise_await_unchecked is only available with ASYNCIFY');
    return 0;
  },
#endif
});