c2875f2017-06-18Martin Nilsson #pike __REAL_VERSION__ //! Module for handling multiple concurrent events. //! //! The @[Future] and @[Promise] API was inspired by //! @url{https://github.com/couchdeveloper/FutureLib@}. protected enum State { STATE_PENDING = 0, STATE_FULFILLED, STATE_REJECTED, }; protected Thread.Mutex mux = Thread.Mutex(); protected Thread.Condition cond = Thread.Condition(); //! Global failure callback, called when a promise without failure //! callback fails. This is useful to log exceptions, so they are not //! just silently caught and ignored. void on_failure(function(mixed : void) f) { global_on_failure = f; } protected function(mixed : void) global_on_failure;
4d481b2017-12-06Stephen R. van den Berg //! @param enable //! Setting this to @expr{false@} causes all @[Concurrent] callbacks //! (except for timeouts) //! to be called directly, without using a backend. //! //! @note //! As long as the backend hasn't started, it will default to @expr{false@}. //! Upon startup of the backend, it will change to @expr{true@} unless you //! explicitly called @[use_backend()] before that. //! //! @note //! (Un)setting this typically alters the order in which some callbacks //! are called (depending on what happens in a callback). final void use_backend(int enable) { callout = enable ? call_out : callnow; remove_call_out(auto_use_backend); } private mixed callnow(function(mixed ...:void) f, int|float delay, mixed ... args) { mixed err = catch (f(@args)); if (err) master()->handle_error(err); return 0; } private void auto_use_backend() { callout = call_out; } protected function(function(mixed ...:void), int|float, mixed ...:mixed) callout; private void create() { callout = callnow; call_out(auto_use_backend, 0); }
c2875f2017-06-18Martin Nilsson //! Value that will be provided asynchronously //! sometime in the future. //! //! @seealso //! @[Promise] class Future {
4d481b2017-12-06Stephen R. van den Berg  mixed result;
c2875f2017-06-18Martin Nilsson  State state;
4d481b2017-12-06Stephen R. van den Berg  protected array(array(function(mixed, mixed ...: void)|mixed))
c2875f2017-06-18Martin Nilsson  success_cbs = ({});
4d481b2017-12-06Stephen R. van den Berg  protected array(array(function(mixed, mixed ...: void)|mixed))
c2875f2017-06-18Martin Nilsson  failure_cbs = ({});
f98f792019-02-13Henrik Grubbström (Grubba)  //! Call a callback function. //! //! @param cb //! Callback function to call. //! //! @param args //! Arguments to call @[cb] with. protected void call_callback(function cb, mixed ... args) { callout(cb, 0, @args); }
c2875f2017-06-18Martin Nilsson  //! Wait for fulfillment and return the value. //! //! @throws //! Throws on rejection. mixed get() { State s = state; mixed res = result; if (!s) {
4d481b2017-12-06Stephen R. van den Berg  Thread.MutexKey key = mux->lock();
c2875f2017-06-18Martin Nilsson  while (!state) { cond->wait(key); } s = state; res = result; } if (s == STATE_REJECTED) { throw(res); } return res; } //! Register a callback that is to be called on fulfillment. //! //! @param cb //! Function to be called. The first argument will be the //! result of the @[Future]. //! //! @param extra //! Any extra context needed for @[cb]. They will be provided //! as arguments two and onwards when @[cb] is called. //! //! @note //! @[cb] will always be called from the main backend. //! //! @seealso //! @[on_failure()] this_program on_success(function(mixed, mixed ... : void) cb, mixed ... extra) {
4d481b2017-12-06Stephen R. van den Berg  switch (state) { case STATE_FULFILLED:
f98f792019-02-13Henrik Grubbström (Grubba)  call_callback(cb, result, @extra);
4d481b2017-12-06Stephen R. van den Berg  break; case STATE_PENDING: // Rely on interpreter lock to add to success_cbs before state changes // again success_cbs += ({ ({ cb, @extra }) });
c2875f2017-06-18Martin Nilsson  }
4d481b2017-12-06Stephen R. van den Berg  return this_program::this;
c2875f2017-06-18Martin Nilsson  } //! Register a callback that is to be called on failure. //! //! @param cb //! Function to be called. The first argument will be the //! failure result of the @[Future]. //! //! @param extra //! Any extra context needed for @[cb]. They will be provided //! as arguments two and onwards when @[cb] is called. //! //! @note //! @[cb] will always be called from the main backend. //! //! @seealso //! @[on_success()] this_program on_failure(function(mixed, mixed ... : void) cb, mixed ... extra) {
4d481b2017-12-06Stephen R. van den Berg  switch (state) { case STATE_REJECTED:
f98f792019-02-13Henrik Grubbström (Grubba)  call_callback(cb, result, @extra);
4d481b2017-12-06Stephen R. van den Berg  break; case STATE_PENDING: // Rely on interpreter lock to add to failure_cbs before state changes // again failure_cbs += ({ ({ cb, @extra }) });
c2875f2017-06-18Martin Nilsson  }
4d481b2017-12-06Stephen R. van den Berg  return this_program::this;
c2875f2017-06-18Martin Nilsson  } //! Apply @[fun] with @[val] followed by the contents of @[ctx], //! and update @[p] with the result. protected void apply(mixed val, Promise p, function(mixed, mixed ... : mixed) fun, array(mixed) ctx) {
4d481b2017-12-06Stephen R. van den Berg  mixed f; if (mixed err = catch (f = fun(val, @ctx))) p->failure(err); else p->success(f);
c2875f2017-06-18Martin Nilsson  } //! Apply @[fun] with @[val] followed by the contents of @[ctx], //! and update @[p] with the eventual result. protected void apply_flat(mixed val, Promise p, function(mixed, mixed ... : Future) fun, array(mixed) ctx) {
4d481b2017-12-06Stephen R. van den Berg  Future f; { if (mixed err = catch (f = fun(val, @ctx))) { p->failure(err); return; } } if (!objectp(f) || !f->on_failure || !f->on_success) error("Expected %O to return a Future. Got: %O.\n", fun, f); f->on_failure(p->failure)->on_success(p->success);
c2875f2017-06-18Martin Nilsson  } //! Apply @[fun] with @[val] followed by the contents of @[ctx], //! and update @[p] with the eventual result. protected void apply_smart(mixed val, Promise p, function(mixed, mixed ... : mixed|Future) fun, array(mixed) ctx) {
4d481b2017-12-06Stephen R. van den Berg  mixed|Future f; { if (mixed err = catch (f = fun(val, @ctx))) { p->failure(err); return; } } if (!objectp(f) || !functionp(f->on_failure) || !functionp(f->on_success)) p->success(f); else f->on_failure(p->failure)->on_success(p->success);
c2875f2017-06-18Martin Nilsson  } //! Apply @[fun] with @[val] followed by the contents of @[ctx], //! and update @[p] with @[val] if @[fun] didn't return false.
4d481b2017-12-06Stephen R. van den Berg  //! If @[fun] returned false, fail @[p] with @expr{0@} as result.
c2875f2017-06-18Martin Nilsson  protected void apply_filter(mixed val, Promise p, function(mixed, mixed ... : int(0..1)) fun, array(mixed) ctx) {
4d481b2017-12-06Stephen R. van den Berg  int bool; mixed err; if (!(err = catch (bool = fun(val, @ctx))) && bool) p->success(val); else p->failure(err);
c2875f2017-06-18Martin Nilsson  }
4d481b2017-12-06Stephen R. van den Berg  //! This specifies a callback that is only called on success, and //! allows you to alter the future. //! //! @param fun //! Function to be called. The first argument will be the //! @b{success@} result of @b{this@} @[Future]. //! The return value will be the success result of the new @[Future]. //! //! @param extra //! Any extra context needed for //! @expr{fun@}. They will be provided //! as arguments two and onwards when the callback is called. //! //! @returns //! The new @[Future].
cfff2a2017-11-24Stephen R. van den Berg  //! //! @note //! This method is used if your @[fun] returns a regular value (i.e. //! @b{not@} a @[Future]). //! //! @seealso
4d481b2017-12-06Stephen R. van den Berg  //! @[map_with()], @[transform()], @[recover()]
c2875f2017-06-18Martin Nilsson  this_program map(function(mixed, mixed ... : mixed) fun, mixed ... extra) { Promise p = Promise(); on_failure(p->failure); on_success(apply, p, fun, extra); return p->future(); }
4d481b2017-12-06Stephen R. van den Berg  //! This specifies a callback that is only called on success, and //! allows you to alter the future. //! //! @param fun //! Function to be called. The first argument will be the //! @b{success@} result of @b{this@} @[Future]. //! The return value must be a @[Future] that promises //! the new result. //! //! @param extra //! Any extra context needed for //! @expr{fun@}. They will be provided //! as arguments two and onwards when the callback is called. //! //! @returns //! The new @[Future].
cfff2a2017-11-24Stephen R. van den Berg  //! //! @note //! This method is used if your @[fun] returns a @[Future] again. //! //! @seealso
4d481b2017-12-06Stephen R. van den Berg  //! @[map()], @[transform_with()], @[recover_with()], @[flat_map] this_program map_with(function(mixed, mixed ... : this_program) fun,
c2875f2017-06-18Martin Nilsson  mixed ... extra) { Promise p = Promise(); on_failure(p->failure); on_success(apply_flat, p, fun, extra); return p->future(); }
4d481b2017-12-06Stephen R. van den Berg  //! This is an alias for @[map_with()]. //! //! @seealso //! @[map_with()] inline this_program flat_map(function(mixed, mixed ... : this_program) fun, mixed ... extra) { return map_with(fun, @extra); } //! This specifies a callback that is only called on failure, and //! allows you to alter the future into a success. //! //! @param fun //! Function to be called. The first argument will be the //! @b{failure@} result of @b{this@} @[Future]. //! The return value will be the success result of the new @[Future]. //! //! @param extra //! Any extra context needed for //! @expr{fun@}. They will be provided //! as arguments two and onwards when the callback is called. //! //! @returns //! The new @[Future].
4eaab52017-11-24Stephen R. van den Berg  //! //! @note
4d481b2017-12-06Stephen R. van den Berg  //! This method is used if your callbacks return a regular value (i.e.
4eaab52017-11-24Stephen R. van den Berg  //! @b{not@} a @[Future]). //! //! @seealso //! @[recover_with()], @[map()], @[transform()]
c2875f2017-06-18Martin Nilsson  this_program recover(function(mixed, mixed ... : mixed) fun, mixed ... extra) { Promise p = Promise(); on_success(p->success); on_failure(apply, p, fun, extra); return p->future(); }
4d481b2017-12-06Stephen R. van den Berg  //! This specifies a callback that is only called on failure, and //! allows you to alter the future into a success. //! //! @param fun //! Function to be called. The first argument will be the //! @b{failure@} result of @b{this@} @[Future]. //! The return value must be a @[Future] that promises //! the new success result. //! //! @param extra //! Any extra context needed for //! @expr{fun@}. They will be provided //! as arguments two and onwards when the callback is called. //! //! @returns //! The new @[Future].
4eaab52017-11-24Stephen R. van den Berg  //! //! @note
4d481b2017-12-06Stephen R. van den Berg  //! This method is used if your callbacks return a @[Future] again.
4eaab52017-11-24Stephen R. van den Berg  //! //! @seealso
4d481b2017-12-06Stephen R. van den Berg  //! @[recover()], @[map_with()], @[transform_with()]
c2875f2017-06-18Martin Nilsson  this_program recover_with(function(mixed, mixed ... : this_program) fun, mixed ... extra) { Promise p = Promise(); on_success(p->success); on_failure(apply_flat, p, fun, extra); return p->future(); }
4d481b2017-12-06Stephen R. van den Berg  //! This specifies a callback that is only called on success, and //! allows you to selectively alter the future into a failure. //! //! @param fun //! Function to be called. The first argument will be the //! @b{success@} result of @b{this@} @[Future]. //! If the return value is @expr{true@}, the future succeeds with //! the original success result. //! If the return value is @expr{false@}, the future fails with //! an @[UNDEFINED] result. //! //! @param extra //! Any extra context needed for //! @expr{fun@}. They will be provided //! as arguments two and onwards when the callback is called. //! //! @returns //! The new @[Future]. //! //! @seealso //! @[transform()]
c2875f2017-06-18Martin Nilsson  this_program filter(function(mixed, mixed ... : int(0..1)) fun, mixed ... extra) { Promise p = Promise(); on_failure(p->failure); on_success(apply_filter, p, fun, extra); return p->future(); }
4d481b2017-12-06Stephen R. van den Berg  //! This specifies callbacks that allow you to alter the future.
c2875f2017-06-18Martin Nilsson  //!
4d481b2017-12-06Stephen R. van den Berg  //! @param success //! Function to be called. The first argument will be the //! @b{success@} result of @b{this@} @[Future]. //! The return value will be the success result of the new @[Future]. //! //! @param failure //! Function to be called. The first argument will be the //! @b{failure@} result of @b{this@} @[Future]. //! The return value will be the success result of the new @[Future]. //! If this callback is omitted, it will default to the same callback as //! @expr{success@}. //! //! @param extra //! Any extra context needed for //! @expr{success@} and @expr{failure@}. They will be provided //! as arguments two and onwards when the callbacks are called. //! //! @returns //! The new @[Future].
c2875f2017-06-18Martin Nilsson  //!
cfff2a2017-11-24Stephen R. van den Berg  //! @note
4d481b2017-12-06Stephen R. van den Berg  //! This method is used if your callbacks return a regular value (i.e.
cfff2a2017-11-24Stephen R. van den Berg  //! @b{not@} a @[Future]). //!
c2875f2017-06-18Martin Nilsson  //! @seealso
4eaab52017-11-24Stephen R. van den Berg  //! @[transform_with()], @[map()], @[recover()]
c2875f2017-06-18Martin Nilsson  this_program transform(function(mixed, mixed ... : mixed) success, function(mixed, mixed ... : mixed)|void failure, mixed ... extra) { Promise p = Promise(); on_success(apply, p, success, extra); on_failure(apply, p, failure || success, extra); return p->future(); }
4d481b2017-12-06Stephen R. van den Berg  //! This specifies callbacks that allow you to alter the future. //! //! @param success //! Function to be called. The first argument will be the //! @b{success@} result of @b{this@} @[Future]. //! The return value must be a @[Future] that promises //! the new result.
c2875f2017-06-18Martin Nilsson  //!
4d481b2017-12-06Stephen R. van den Berg  //! @param failure //! Function to be called. The first argument will be the //! @b{failure@} result of @b{this@} @[Future]. //! The return value must be a @[Future] that promises //! the new success result. //! If this callback is omitted, it will default to the same callback as //! @expr{success@}. //! //! @param extra //! Any extra context needed for //! @expr{success@} and @expr{failure@}. They will be provided //! as arguments two and onwards when the callbacks are called. //! //! @returns //! The new @[Future].
c2875f2017-06-18Martin Nilsson  //!
cfff2a2017-11-24Stephen R. van den Berg  //! @note
4d481b2017-12-06Stephen R. van den Berg  //! This method is used if your callbacks return a @[Future] again.
cfff2a2017-11-24Stephen R. van den Berg  //!
c2875f2017-06-18Martin Nilsson  //! @seealso
4d481b2017-12-06Stephen R. van den Berg  //! @[transform()], @[map_with()], @[recover_with]
cfff2a2017-11-24Stephen R. van den Berg  this_program transform_with(function(mixed, mixed ... : this_program) success, function(mixed, mixed ... : this_program)|void failure,
c2875f2017-06-18Martin Nilsson  mixed ... extra) { Promise p = Promise(); on_success(apply_flat, p, success, extra); on_failure(apply_flat, p, failure || success, extra); return p->future(); }
4d481b2017-12-06Stephen R. van den Berg  //! @param others //! The other futures (results) you want to append. //!
c2875f2017-06-18Martin Nilsson  //! @returns
4d481b2017-12-06Stephen R. van den Berg  //! A new @[Future] that will be fulfilled with an
c2875f2017-06-18Martin Nilsson  //! array of the fulfilled result of this object followed
4d481b2017-12-06Stephen R. van den Berg  //! by the fulfilled results of other futures.
c2875f2017-06-18Martin Nilsson  //! //! @seealso //! @[results()]
4d481b2017-12-06Stephen R. van den Berg  this_program zip(array(this_program) others)
c2875f2017-06-18Martin Nilsson  {
4d481b2017-12-06Stephen R. van den Berg  if (sizeof(others)) return results(({ this_program::this }) + others); return this_program::this; } inline variant this_program zip(this_program ... others) { return zip(others);
c2875f2017-06-18Martin Nilsson  } //! JavaScript Promise API close but not identical equivalent //! of a combined @[transform()] and @[transform_with()]. //! //! @param onfulfilled //! Function to be called on fulfillment. The first argument will be the //! result of @b{this@} @[Future]. //! The return value will be the result of the new @[Future]. //! If the return value already is a @[Future], pass it as-is. //! //! @param onrejected //! Function to be called on failure. The first argument will be the //! failure result of @b{this@} @[Future]. //! The return value will be the failure result of the new @[Future]. //! If the return value already is a @[Future], pass it as-is. //! //! @param extra //! Any extra context needed for @expr{onfulfilled@} and //! @expr{onrejected@}. They will be provided //! as arguments two and onwards when the callbacks are called. //! //! @returns //! The new @[Future]. //! //! @seealso //! @[transform()], @[transform_with()], @[thencatch()], //! @[on_success()], @[Promise.success()], //! @[on_failure()], @[Promise.failure()], //! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@} this_program then(void|function(mixed, mixed ... : mixed) onfulfilled, void|function(mixed, mixed ... : mixed) onrejected, mixed ... extra) { Promise p = Promise(); if (onfulfilled) on_success(apply_smart, p, onfulfilled, extra); else on_success(p->success); if (onrejected) on_failure(apply_smart, p, onrejected, extra); else on_failure(p->failure); return p->future(); } //! JavaScript Promise API equivalent of a combination of @[recover()] //! and @[recover_with()]. //! //! @param onrejected //! Function to be called. The first argument will be the //! failure result of @b{this@} @[Future]. //! The return value will the failure result of the new @[Future]. //! If the return value already is a @[Future], pass it as-is. //! //! @param extra //! Any extra context needed for //! @expr{onrejected@}. They will be provided //! as arguments two and onwards when the callback is called. //! //! @returns //! The new @[Future]. //! //! @seealso //! @[recover()], @[recover_with()], @[then()], @[on_failure()], //! @[Promise.failure()], //! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@} inline this_program thencatch(function(mixed, mixed ... : mixed) onrejected, mixed ... extra) { return then(0, onrejected, @extra); } //! Return a @[Future] that will either be fulfilled with the fulfilled //! result of this @[Future], or be failed after @[seconds] have expired. this_program timeout(int|float seconds) { Promise p = Promise(); on_failure(p->failure); on_success(p->success); call_out(p->try_failure, seconds, ({ "Timeout.\n", backtrace() })); return p->future(); } protected string _sprintf(int t) { return t=='O' && sprintf("%O(%s,%O)", this_program, ([ STATE_PENDING : "pending", STATE_REJECTED : "rejected", STATE_FULFILLED : "fulfilled" ])[state], result); } }
4d481b2017-12-06Stephen R. van den Berg class AggregateState { private Promise promise; private int(0..) promises; private int(0..) succeeded, failed; final array(mixed) results; final int(0..) min_failures; final int(-1..) max_failures; final mixed accumulator; final function(mixed, mixed, mixed ... : mixed) fold_fun; final array(mixed) extra; private void create(Promise p) { if (p->_materialised || p->_materialised++) error("Cannot materialise a Promise more than once.\n"); promise = p; } final void materialise() { if (promise->_astate) { promise->_astate = 0; if (results) { promises = sizeof(results); array(Future) futures = results; if (fold_fun) results = 0; foreach(futures; int idx; Future f) f->on_failure(cb_failure, idx)->on_success(cb_success, idx); } } } private void fold_one(mixed val) { mixed err = catch (accumulator = fold_fun(val, accumulator, @extra)); if (err && promise) promise->failure(err); } private void fold(function(mixed:void) failsucc) { failsucc(fold_fun ? accumulator : results); results = 0; // Free memory } private void cb_failure(mixed value, int idx) { Promise p; // Cache it, to cover a failure race if (p = promise) { Thread.MutexKey key = mux->lock(); do { if (!p->state) { ++failed; if (max_failures < failed && max_failures >= 0) { key = 0; p->try_failure(value); break; } int success = succeeded + failed == promises; key = 0; if (results) results[idx] = value; else fold_one(value); if (success) { fold(failed >= min_failures ? p->success : p->failure); break; } } return; } while (0); promise = 0; // Free backreference } } private void cb_success(mixed value, int idx) { Promise p; // Cache it, to cover a failure race if (p = promise) { Thread.MutexKey key = mux->lock(); do { if (!p->state) { ++succeeded; if (promises - min_failures < succeeded) { key = 0; p->try_failure(value); break; } int success = succeeded + failed == promises; key = 0; if (results) results[idx] = value; else fold_one(value); if (success) { fold(p->success); break; } } return; } while (0); promise = 0; // Free backreference } } }
c2875f2017-06-18Martin Nilsson //! Promise to provide a @[Future] value. //! //! Objects of this class are typically kept internal to the //! code that provides the @[Future] value. The only thing //! that is directly returned to the user is the return //! value from @[future()]. //! //! @seealso //! @[Future], @[future()] class Promise { inherit Future;
4d481b2017-12-06Stephen R. van den Berg  final int _materialised; final AggregateState _astate;
c2875f2017-06-18Martin Nilsson  //! Creates a new promise, optionally initialised from a traditional callback //! driven method via @expr{executor(resolve, reject, extra ... )@}. //! //! @seealso //! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@} protected void create(void| function(function(mixed:void),
4d481b2017-12-06Stephen R. van den Berg  function(mixed:void), mixed ...:void) executor, mixed ... extra) {
c2875f2017-06-18Martin Nilsson  if (executor) executor(success, failure, @extra); }
4d481b2017-12-06Stephen R. van den Berg  Future on_success(function(mixed, mixed ... : void) cb, mixed ... extra) { if (_astate) _astate->materialise(); return ::on_success(cb, @extra); } Future on_failure(function(mixed, mixed ... : void) cb, mixed ... extra) { if (_astate) _astate->materialise(); return ::on_failure(cb, @extra); } mixed get() { if (_astate) _astate->materialise(); return ::get(); }
c2875f2017-06-18Martin Nilsson  //! The future value that we promise. Future future() { return Future::this; }
4d481b2017-12-06Stephen R. van den Berg  protected this_program finalise(State newstate, mixed value, int try, array(array(function(mixed, mixed ...: void)|array(mixed))) cbs, void|function(mixed : void) globalfailure)
c2875f2017-06-18Martin Nilsson  {
4d481b2017-12-06Stephen R. van den Berg  Thread.MutexKey key = mux->lock(); if (!state) { state = newstate;
c2875f2017-06-18Martin Nilsson  result = value;
4d481b2017-12-06Stephen R. van den Berg  key = 0;
c2875f2017-06-18Martin Nilsson  cond->broadcast();
4d481b2017-12-06Stephen R. van den Berg  if (sizeof(cbs)) { foreach(cbs; ; array cb) if (cb)
f98f792019-02-13Henrik Grubbström (Grubba)  call_callback(cb[0], value, @cb[1..]);
c2875f2017-06-18Martin Nilsson  }
4d481b2017-12-06Stephen R. van den Berg  else if (globalfailure)
f98f792019-02-13Henrik Grubbström (Grubba)  call_callback(globalfailure, value);
4d481b2017-12-06Stephen R. van den Berg  failure_cbs = success_cbs = 0; // Free memory and references } else { key = 0; if (!try) error("Promise has already been finalised.\n");
c2875f2017-06-18Martin Nilsson  }
4d481b2017-12-06Stephen R. van den Berg  return this_program::this;
c2875f2017-06-18Martin Nilsson  }
4d481b2017-12-06Stephen R. van den Berg  //! @decl this_program success(mixed value) //!
c2875f2017-06-18Martin Nilsson  //! Fulfill the @[Future]. //! //! @param value //! Result of the @[Future]. //! //! @throws //! Throws an error if the @[Future] already has been fulfilled //! or failed. //! //! Mark the @[Future] as fulfilled, and schedule the @[on_success()] //! callbacks to be called as soon as possible. //! //! @seealso //! @[try_success()], @[try_failure()], @[failure()], @[on_success()]
4d481b2017-12-06Stephen R. van den Berg  this_program success(mixed value, void|int try)
c2875f2017-06-18Martin Nilsson  {
4d481b2017-12-06Stephen R. van den Berg  return finalise(STATE_FULFILLED, value, try, success_cbs);
c2875f2017-06-18Martin Nilsson  } //! Fulfill the @[Future] if it hasn't been fulfilled or failed already. //! //! @param value //! Result of the @[Future]. //! //! Mark the @[Future] as fulfilled if it hasn't already been fulfilled //! or failed, and in that case schedule the @[on_success()] callbacks //! to be called as soon as possible. //! //! @seealso //! @[success()], @[try_failure()], @[failure()], @[on_success()]
4d481b2017-12-06Stephen R. van den Berg  inline this_program try_success(mixed value) { return state ? this_program::this : success(value, 1);
c2875f2017-06-18Martin Nilsson  }
4d481b2017-12-06Stephen R. van den Berg  //! @decl this_program failure(mixed value) //!
c2875f2017-06-18Martin Nilsson  //! Reject the @[Future] value. //! //! @param value //! Failure result of the @[Future]. //! //! @throws //! Throws an error if the @[Future] already has been fulfilled //! or failed. //! //! Mark the @[Future] as failed, and schedule the @[on_failure()] //! callbacks to be called as soon as possible. //! //! @seealso //! @[try_failure()], @[success()], @[on_failure()]
4d481b2017-12-06Stephen R. van den Berg  this_program failure(mixed value, void|int try)
c2875f2017-06-18Martin Nilsson  {
4d481b2017-12-06Stephen R. van den Berg  return finalise(STATE_REJECTED, value, try, failure_cbs, global_on_failure);
c2875f2017-06-18Martin Nilsson  } //! Maybe reject the @[Future] value. //! //! @param value //! Failure result of the @[Future]. //! //! Mark the @[Future] as failed if it hasn't already been fulfilled, //! and in that case schedule the @[on_failure()] callbacks to be //! called as soon as possible. //! //! @seealso //! @[failure()], @[success()], @[on_failure()]
4d481b2017-12-06Stephen R. van den Berg  inline this_program try_failure(mixed value)
c2875f2017-06-18Martin Nilsson  {
4d481b2017-12-06Stephen R. van den Berg  return state ? this_program::this : failure(value, 1);
c2875f2017-06-18Martin Nilsson  }
4d481b2017-12-06Stephen R. van den Berg  inline private void fill_astate() { if (!_astate) _astate = AggregateState(this); } //! Add futures to the list of futures which the current object depends upon. //! //! If called without arguments it will produce a new @[Future] //! from a new @[Promise] which is implictly added to the dependency list. //! //! @param futures //! The list of @expr{futures@} we want to add to the list we depend upon. //! //! @returns //! The new @[Promise]. //! //! @note //! Can be called multiple times to add more. //! //! @note //! Once the promise has been materialised (when either @[on_success()], //! @[on_failure()] or @[get()] has been called on this object), it is //! not possible to call @[depend()] anymore. //! //! @seealso //! @[fold()], @[first_completed()], @[max_failures()], @[min_failures()], //! @[any_results()], @[Concurrent.results()], @[Concurrent.all()] this_program depend(array(Future) futures)
c2875f2017-06-18Martin Nilsson  {
4d481b2017-12-06Stephen R. van den Berg  if (sizeof(futures)) { fill_astate(); _astate->results += futures;
c2875f2017-06-18Martin Nilsson  }
4d481b2017-12-06Stephen R. van den Berg  return this_program::this; } inline variant this_program depend(Future ... futures) { return depend(futures); } variant this_program depend() { Promise p = Promise(); depend(p->future()); return p;
c2875f2017-06-18Martin Nilsson  }
4d481b2017-12-06Stephen R. van den Berg  //! @param initial //! Initial value of the accumulator. //! //! @param fun //! Function to apply. The first argument is the result of //! one of the @[futures]. The second argument is the current value //! of the accumulator. //! //! @param extra //! Any extra context needed for @[fun]. They will be provided //! as arguments three and onwards when @[fun] is called. //! //! @returns //! The new @[Promise]. //! //! @note //! If @[fun] throws an error it will fail the @[Future]. //! //! @note //! @[fun] may be called in any order, and will be called //! once for every @[Future] it depends upon, unless one of the //! calls fails in which case no further calls will be //! performed. //! //! @seealso //! @[depend()], @[Concurrent.fold()] this_program fold(mixed initial, function(mixed, mixed, mixed ... : mixed) fun, mixed ... extra) { if (_astate) { _astate->accumulator = initial; _astate->fold_fun = fun; _astate->extra = extra; _astate->materialise(); } else success(initial); return this_program::this; }
c2875f2017-06-18Martin Nilsson 
4d481b2017-12-06Stephen R. van den Berg  //! It evaluates to the first future that completes of the list //! of futures it depends upon. //! //! @returns //! The new @[Promise]. //! //! @seealso //! @[depend()], @[Concurrent.first_completed()] this_program first_completed()
c2875f2017-06-18Martin Nilsson  {
4d481b2017-12-06Stephen R. van den Berg  if (_astate) { _astate->results->on_failure(try_failure)->on_success(try_success); _astate = 0; } else success(0); return this_program::this; } //! @param max //! Specifies the maximum number of failures to be accepted in //! the list of futures this promise depends upon. //! //! @expr{-1@} means unlimited. //! //! Defaults to @expr{0@}. //! //! @returns //! The new @[Promise]. //! //! @seealso //! @[depend()], @[min_failures()], @[any_results()] this_program max_failures(int(-1..) max) { fill_astate(); _astate->max_failures = max; return this_program::this; } //! @param min //! Specifies the minimum number of failures to be required in //! the list of futures this promise depends upon. Defaults //! to @expr{0@}. //! //! @returns //! The new @[Promise]. //! //! @seealso //! @[depend()], @[max_failures()] this_program min_failures(int(0..) min) { fill_astate(); _astate->min_failures = min; return this_program::this; } //! Sets the number of failures to be accepted in the list of futures //! this promise //! depends upon to unlimited. It is equivalent to @expr{max_failures(-1)@}. //! //! @returns //! The new @[Promise]. //! //! @seealso //! @[depend()], @[max_failures()] this_program any_results() { return max_failures(-1); } protected void destroy() { if (!state) try_failure(({ "Promise broken.\n", backtrace() }));
c2875f2017-06-18Martin Nilsson  } } //! @returns //! A @[Future] that represents the first //! of the @expr{futures@} that completes. //! //! @seealso
4d481b2017-12-06Stephen R. van den Berg //! @[race()], @[Promise.first_completed()]
c2875f2017-06-18Martin Nilsson variant Future first_completed(array(Future) futures) {
4d481b2017-12-06Stephen R. van den Berg  return Promise()->depend(futures)->first_completed()->future();
c2875f2017-06-18Martin Nilsson } variant inline Future first_completed(Future ... futures) { return first_completed(futures); } //! JavaScript Promise API equivalent of @[first_completed()]. //! //! @seealso
4d481b2017-12-06Stephen R. van den Berg //! @[first_completed()], @[Promise.first_completed()]
c2875f2017-06-18Martin Nilsson //! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@} variant inline Future race(array(Future) futures) { return first_completed(futures); } variant inline Future race(Future ... futures) { return first_completed(futures); } //! @returns //! A @[Future] that represents the array of all the completed @expr{futures@}. //! //! @seealso
4d481b2017-12-06Stephen R. van den Berg //! @[all()], @[Promise.depend()]
c2875f2017-06-18Martin Nilsson variant Future results(array(Future) futures) {
c1cf1e2018-04-09Martin Karlgren  if(!sizeof(futures)) return resolve(({}));
4d481b2017-12-06Stephen R. van den Berg  return Promise()->depend(futures)->future();
c2875f2017-06-18Martin Nilsson } inline variant Future results(Future ... futures) { return results(futures); } //! JavaScript Promise API equivalent of @[results()]. //! //! @seealso
4d481b2017-12-06Stephen R. van den Berg //! @[results()], @[Promise.depend()]
c2875f2017-06-18Martin Nilsson //! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@} inline variant Future all(array(Future) futures) { return results(futures); } inline variant Future all(Future ... futures) { return results(futures); } //! @returns //! A new @[Future] that has already failed for the specified @expr{reason@}. //! //! @seealso //! @[Future.on_failure()], @[Promise.failure()] //! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@} Future reject(mixed reason) {
4d481b2017-12-06Stephen R. van den Berg  return Promise()->failure(reason)->future();
c2875f2017-06-18Martin Nilsson } //! @returns //! A new @[Future] that has already been fulfilled with @expr{value@} //! as result. If @expr{value@} is an object which already //! has @[on_failure] and @[on_success] methods, return it unchanged. //! //! @note //! This function can be used to ensure values are futures. //! //! @seealso //! @[Future.on_success()], @[Promise.success()] //! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@} Future resolve(mixed value) { if (objectp(value) && value->on_failure && value->on_success) return value;
4d481b2017-12-06Stephen R. van den Berg  return Promise()->success(value)->future();
c2875f2017-06-18Martin Nilsson } //! Return a @[Future] that represents the array of mapping @[fun] //! over the results of the completed @[futures]. Future traverse(array(Future) futures, function(mixed, mixed ... : mixed) fun, mixed ... extra) { return results(futures->map(fun, @extra)); } //! Return a @[Future] that represents the accumulated results of //! applying @[fun] to the results of the @[futures] in turn. //! //! @param initial //! Initial value of the accumulator. //! //! @param fun //! Function to apply. The first argument is the result of //! one of the @[futures], the second the current accumulated //! value, and any further from @[extra]. //! //! @note //! If @[fun] throws an error it will fail the @[Future]. //! //! @note //! @[fun] may be called in any order, and will be called //! once for every @[Future] in @[futures], unless one of //! calls fails in which case no further calls will be //! performed. Future fold(array(Future) futures, mixed initial, function(mixed, mixed, mixed ... : mixed) fun, mixed ... extra) {
4d481b2017-12-06Stephen R. van den Berg  return Promise()->depend(futures)->fold(initial, fun, extra)->future();
c2875f2017-06-18Martin Nilsson }