5f16f22016-11-05Martin Nilsson #pike __REAL_VERSION__
9654f72016-04-12Henrik Grubbström (Grubba) //! Module for handling multiple concurrent events.
31dd442016-06-15Henrik Grubbström (Grubba) //! //! The @[Future] and @[Promise] API was inspired by //! @url{https://github.com/couchdeveloper/FutureLib@}.
c60ad22016-04-13Henrik Grubbström (Grubba) 
9654f72016-04-12Henrik Grubbström (Grubba) protected enum State { STATE_PENDING = 0, STATE_FULFILLED, STATE_REJECTED, }; protected Thread.Mutex mux = Thread.Mutex(); protected Thread.Condition cond = Thread.Condition();
7affa12017-02-24Martin Nilsson //! 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.
c76f642017-02-24Martin Nilsson void on_failure(function(mixed : void) f)
7affa12017-02-24Martin Nilsson { global_on_failure = f; }
c76f642017-02-24Martin Nilsson protected function(mixed : void) global_on_failure;
7affa12017-02-24Martin Nilsson 
9654f72016-04-12Henrik Grubbström (Grubba) //! Value that will be provided asynchronously //! sometime in the future. //! //! @seealso //! @[Promise] class Future { mixed result = UNDEFINED; State state;
bf700d2016-10-26Henrik Grubbström (Grubba)  protected array(array(function(mixed, mixed ...: void)|array(mixed))) success_cbs = ({}); protected array(array(function(mixed, mixed ...: void)|array(mixed))) failure_cbs = ({});
9654f72016-04-12Henrik Grubbström (Grubba) 
6b01882016-11-09Stephen R. van den Berg  //! Wait for fulfillment and return the value.
9654f72016-04-12Henrik Grubbström (Grubba)  //! //! @throws //! Throws on rejection. mixed get() { State s = state; mixed res = result; if (!s) { object key = mux->lock(); while (!state) { cond->wait(key); } s = state; res = result; key = 0; } if (s == STATE_REJECTED) { throw(res); } return res; } //! Register a callback that is to be called on fulfillment.
bf700d2016-10-26Henrik Grubbström (Grubba)  //! //! @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()]
47c3112016-05-10Pontus Östlund  this_program on_success(function(mixed, mixed ... : void) cb, mixed ... extra)
9654f72016-04-12Henrik Grubbström (Grubba)  { object key = mux->lock();
bf700d2016-10-26Henrik Grubbström (Grubba)  if (state == STATE_FULFILLED) { call_out(cb, 0, result, @extra); key = 0; return this;
9654f72016-04-12Henrik Grubbström (Grubba)  }
47c3112016-05-10Pontus Östlund 
bf700d2016-10-26Henrik Grubbström (Grubba)  success_cbs += ({ ({ cb, extra }) }); key = 0;
47c3112016-05-10Pontus Östlund  return this;
9654f72016-04-12Henrik Grubbström (Grubba)  } //! Register a callback that is to be called on failure.
bf700d2016-10-26Henrik Grubbström (Grubba)  //! //! @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()]
47c3112016-05-10Pontus Östlund  this_program on_failure(function(mixed, mixed ... : void) cb, mixed ... extra)
9654f72016-04-12Henrik Grubbström (Grubba)  { object key = mux->lock();
bf700d2016-10-26Henrik Grubbström (Grubba)  if (state == STATE_REJECTED) { call_out(cb, 0, result, @extra); key = 0; return this;
9654f72016-04-12Henrik Grubbström (Grubba)  }
bf700d2016-10-26Henrik Grubbström (Grubba)  failure_cbs += ({ ({ cb, extra }) }); key = 0;
47c3112016-05-10Pontus Östlund  return this;
9654f72016-04-12Henrik Grubbström (Grubba)  }
c60ad22016-04-13Henrik Grubbström (Grubba)  //! 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) { mixed err = catch { p->success(fun(val, @ctx)); return; }; p->failure(err); } //! 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) { mixed err = catch { Future f = fun(val, @ctx); 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); f->on_success(p->success); return; }; p->failure(err); } //! Apply @[fun] with @[val] followed by the contents of @[ctx],
ccbf012016-12-01Stephen R. van den Berg  //! and update @[p] with the eventual result. protected void apply_smart(mixed val, Promise p, function(mixed, mixed ... : mixed|Future) fun, array(mixed) ctx) { mixed err = catch { mixed|Future f = fun(val, @ctx); if (!objectp(f) || !functionp(f->on_failure) || !functionp(f->on_success)) { p->success(f); return; } f->on_failure(p->failure); f->on_success(p->success); return; }; p->failure(err); } //! Apply @[fun] with @[val] followed by the contents of @[ctx],
c60ad22016-04-13Henrik Grubbström (Grubba)  //! and update @[p] with @[val] if @[fun] didn't return false. //! If @[fun] returned false fail @[p] with @[UNDEFINED]. protected void apply_filter(mixed val, Promise p, function(mixed, mixed ... : int(0..1)) fun, array(mixed) ctx) { mixed err = catch { if (fun(val, @ctx)) { p->success(val); } else { p->failure(UNDEFINED); } return; }; p->failure(err); } //! Return a @[Future] that will be fulfilled with the result //! of applying @[fun] with the fulfilled result of this @[Future] //! followed by @[extra]. 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(); } //! Return a @[Future] that will be fulfilled with the fulfilled result //! of applying @[fun] with the fulfilled result of this @[Future] //! followed by @[extra]. this_program flat_map(function(mixed, mixed ... : this_program) fun, mixed ... extra) { Promise p = Promise(); on_failure(p->failure); on_success(apply_flat, p, fun, extra); return p->future(); } //! Return a @[Future] that will be fulfilled with either
cc178f2016-05-18Henrik Grubbström (Grubba)  //! the fulfilled result of this @[Future], or the result
c60ad22016-04-13Henrik Grubbström (Grubba)  //! of applying @[fun] with the failed result followed //! by @[extra]. 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(); } //! Return a @[Future] that will be fulfilled with either
cc178f2016-05-18Henrik Grubbström (Grubba)  //! the fulfilled result of this @[Future], or the fulfilled result
c60ad22016-04-13Henrik Grubbström (Grubba)  //! of applying @[fun] with the failed result followed //! by @[extra]. 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(); } //! Return a @[Future] that either will by fulfilled by the //! fulfilled result of this @[Future] if applying @[fun] //! with the result followed by @[extra] returns true, //! or will fail with @[UNDEFINED] if it returns false. 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(); } //! Return a @[Future] that will be fulfilled with either //! the result of applying @[success] with the fulfilled result //! followed by @[extra], or the result of applying @[failure] //! with the failed result followed by @[extra]. //! //! @[failure] defaults to @[success]. //! //! @seealso //! @[map] 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(); } //! Return a @[Future] that will be fulfilled with either //! the fulfilled result of applying @[success] with the fulfilled result //! followed by @[extra], or the fulfilled result of applying @[failure] //! with the failed result followed by @[extra]. //! //! @[failure] defaults to @[success]. //! //! @seealso //! @[flat_map] this_program transform_with(function(mixed, mixed ... : Future) success, function(mixed, mixed ... : Future)|void failure, mixed ... extra) { Promise p = Promise(); on_success(apply_flat, p, success, extra); on_failure(apply_flat, p, failure || success, extra); return p->future(); }
aa51262016-04-18Henrik Grubbström (Grubba) 
6b01882016-11-09Stephen R. van den Berg  //! @returns //! A @[Future] that will be fulfilled with an
aa51262016-04-18Henrik Grubbström (Grubba)  //! array of the fulfilled result of this object followed //! by the fulfilled results of @[others]. //! //! @seealso //! @[results()] this_program zip(this_program ... others) {
765e832016-04-23Henrik Grubbström (Grubba)  if (!sizeof(others)) return this_program::this; return results(({ this_program::this }) + others);
aa51262016-04-18Henrik Grubbström (Grubba)  }
3bce162016-06-02Henrik Grubbström (Grubba) 
6e27292016-11-18Stephen R. van den Berg  //! JavaScript Promise API close but not identical equivalent
ccbf012016-12-01Stephen R. van den Berg  //! of a combined @[transform()] and @[transform_with()].
6e27292016-11-18Stephen R. van den Berg  //!
6b01882016-11-09Stephen R. van den Berg  //! @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].
ccbf012016-12-01Stephen R. van den Berg  //! If the return value already is a @[Future], pass it as-is.
6b01882016-11-09Stephen R. van den Berg  //! //! @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].
ccbf012016-12-01Stephen R. van den Berg  //! If the return value already is a @[Future], pass it as-is.
6b01882016-11-09Stephen R. van den Berg  //! //! @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
ccbf012016-12-01Stephen R. van den Berg  //! @[transform()], @[transform_with()], @[thencatch()], //! @[on_success()], @[Promise.success()], //! @[on_failure()], @[Promise.failure()],
6b01882016-11-09Stephen R. van den Berg  //! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@}
1d407b2016-11-18Stephen R. van den Berg  this_program then(void|function(mixed, mixed ... : mixed) onfulfilled,
6b01882016-11-09Stephen R. van den Berg  void|function(mixed, mixed ... : mixed) onrejected,
6e27292016-11-18Stephen R. van den Berg  mixed ... extra) {
6b01882016-11-09Stephen R. van den Berg  Promise p = Promise();
6e27292016-11-18Stephen R. van den Berg  if (onfulfilled)
ccbf012016-12-01Stephen R. van den Berg  on_success(apply_smart, p, onfulfilled, extra);
6e27292016-11-18Stephen R. van den Berg  else on_success(p->success); if (onrejected)
ccbf012016-12-01Stephen R. van den Berg  on_failure(apply_smart, p, onrejected, extra);
6e27292016-11-18Stephen R. van den Berg  else on_failure(p->failure);
6b01882016-11-09Stephen R. van den Berg  return p->future(); }
ccbf012016-12-01Stephen R. van den Berg  //! JavaScript Promise API equivalent of a combination of @[recover()] //! and @[recover_with()].
6e27292016-11-18Stephen R. van den Berg  //!
6b01882016-11-09Stephen R. van den Berg  //! @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].
ccbf012016-12-01Stephen R. van den Berg  //! If the return value already is a @[Future], pass it as-is.
6b01882016-11-09Stephen R. van den Berg  //! //! @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
ccbf012016-12-01Stephen R. van den Berg  //! @[recover()], @[recover_with()], @[then()], @[on_failure()], //! @[Promise.failure()],
6b01882016-11-09Stephen R. van den Berg  //! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@}
6e27292016-11-18Stephen R. van den Berg  inline this_program thencatch(function(mixed, mixed ... : mixed) onrejected,
6b01882016-11-09Stephen R. van den Berg  mixed ... extra) {
ccbf012016-12-01Stephen R. van den Berg  return then(0, onrejected, @extra);
6b01882016-11-09Stephen R. van den Berg  }
bf700d2016-10-26Henrik Grubbström (Grubba)  //! Return a @[Future] that will either be fulfilled with the fulfilled //! result of this @[Future], or be failed after @[seconds] have expired.
3bce162016-06-02Henrik Grubbström (Grubba)  this_program timeout(int|float seconds) { Promise p = Promise(); on_failure(p->failure); on_success(p->success);
82356b2016-10-26Henrik Grubbström (Grubba)  call_out(p->try_failure, seconds, ({ "Timeout.\n", backtrace() }));
3bce162016-06-02Henrik Grubbström (Grubba)  return p->future(); }
18b8b22017-06-08Martin Nilsson  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); }
9654f72016-04-12Henrik Grubbström (Grubba) } //! Promise to provide a @[Future] value. //!
c60ad22016-04-13Henrik Grubbström (Grubba) //! 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()]. //!
9654f72016-04-12Henrik Grubbström (Grubba) //! @seealso
c60ad22016-04-13Henrik Grubbström (Grubba) //! @[Future], @[future()]
9654f72016-04-12Henrik Grubbström (Grubba) class Promise { inherit Future;
d15f0c2017-11-24Stephen R. van den Berg  protected array astate;
dbf05a2016-12-03Stephen R. van den Berg  //! 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), function(mixed:void), mixed ...:void) executor, mixed ... extra) { if (executor) executor(success, failure, @extra); }
9654f72016-04-12Henrik Grubbström (Grubba)  //! The future value that we promise. Future future() { return Future::this; }
dd2f1a2016-05-31Henrik Grubbström (Grubba)  protected void unlocked_success(mixed value)
9654f72016-04-12Henrik Grubbström (Grubba)  { if (state < STATE_REJECTED) { result = value; state = STATE_FULFILLED; cond->broadcast();
bf700d2016-10-26Henrik Grubbström (Grubba)  foreach(success_cbs, [function(mixed, mixed ...: void) cb, array(mixed) extra]) { if (cb) { call_out(cb, 0, value, @extra); }
9654f72016-04-12Henrik Grubbström (Grubba)  } } }
bf700d2016-10-26Henrik Grubbström (Grubba)  //! Fulfill the @[Future]. //! //! @param value //! Result of the @[Future]. //!
82356b2016-10-26Henrik Grubbström (Grubba)  //! @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.
bf700d2016-10-26Henrik Grubbström (Grubba)  //! //! @seealso
82356b2016-10-26Henrik Grubbström (Grubba)  //! @[try_success()], @[try_failure()], @[failure()], @[on_success()]
dd2f1a2016-05-31Henrik Grubbström (Grubba)  void success(mixed value)
9654f72016-04-12Henrik Grubbström (Grubba)  {
82356b2016-10-26Henrik Grubbström (Grubba)  if (state) error("Promise has already been finalized.\n"); object key = mux->lock(); if (state) error("Promise has already been finalized.\n"); unlocked_success(value); key = 0; } //! 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()] void try_success(mixed value) { if (state) return;
9654f72016-04-12Henrik Grubbström (Grubba)  object key = mux->lock();
82356b2016-10-26Henrik Grubbström (Grubba)  if (state) return;
dd2f1a2016-05-31Henrik Grubbström (Grubba)  unlocked_success(value); key = 0; } protected void unlocked_failure(mixed value) {
9654f72016-04-12Henrik Grubbström (Grubba)  state = STATE_REJECTED; result = value; cond->broadcast();
7affa12017-02-24Martin Nilsson  if( !sizeof(failure_cbs) && global_on_failure ) call_out(global_on_failure, 0, value);
bf700d2016-10-26Henrik Grubbström (Grubba)  foreach(failure_cbs, [function(mixed, mixed ...: void) cb, array(mixed) extra]) { if (cb) { call_out(cb, 0, value, @extra); }
9654f72016-04-12Henrik Grubbström (Grubba)  } }
dd2f1a2016-05-31Henrik Grubbström (Grubba)  //! Reject the @[Future] value.
bf700d2016-10-26Henrik Grubbström (Grubba)  //! //! @param value //! Failure result of the @[Future]. //!
82356b2016-10-26Henrik Grubbström (Grubba)  //! @throws //! Throws an error if the @[Future] already has been fulfilled //! or failed. //!
bf700d2016-10-26Henrik Grubbström (Grubba)  //! Mark the @[Future] as failed, and schedule the @[on_failure()] //! callbacks to be called as soon as possible. //! //! @seealso
82356b2016-10-26Henrik Grubbström (Grubba)  //! @[try_failure()], @[success()], @[on_failure()]
dd2f1a2016-05-31Henrik Grubbström (Grubba)  void failure(mixed value) {
82356b2016-10-26Henrik Grubbström (Grubba)  if (state) error("Promise has already been finalized.\n");
dd2f1a2016-05-31Henrik Grubbström (Grubba)  object key = mux->lock();
82356b2016-10-26Henrik Grubbström (Grubba)  if (state) error("Promise has already been finalized.\n");
dd2f1a2016-05-31Henrik Grubbström (Grubba)  unlocked_failure(value); key = 0; }
bf700d2016-10-26Henrik Grubbström (Grubba)  //! Maybe reject the @[Future] value. //! //! @param value //! Failure result of the @[Future]. //! //! Mark the @[Future] as failed if it hasn't already been fulfilled,
82356b2016-10-26Henrik Grubbström (Grubba)  //! and in that case schedule the @[on_failure()] callbacks to be //! called as soon as possible.
bf700d2016-10-26Henrik Grubbström (Grubba)  //! //! @seealso //! @[failure()], @[success()], @[on_failure()]
82356b2016-10-26Henrik Grubbström (Grubba)  void try_failure(mixed value)
126bb32016-06-01Henrik Grubbström (Grubba)  {
82356b2016-10-26Henrik Grubbström (Grubba)  if (state) return;
126bb32016-06-01Henrik Grubbström (Grubba)  object key = mux->lock();
5e6a6b2016-10-26Henrik Grubbström (Grubba)  if (state) return;
126bb32016-06-01Henrik Grubbström (Grubba)  unlocked_failure(value); }
d15f0c2017-11-24Stephen R. van den Berg  //! Add futures which the current object depends on. //! //! If called without arguments it will produce a new @[Future] //! from a new @[Promise] which is implictly added to the dependency list.
0d613e2017-11-23Stephen R. van den Berg  //! //! @param futures //! The list of all the @expr{futures@} we depend on. //!
d15f0c2017-11-24Stephen R. van den Berg  //! @returns //! The new @[Promise]. //!
0d613e2017-11-23Stephen R. van den Berg  //! @seealso //! @[Concurrent.results()]
d15f0c2017-11-24Stephen R. van den Berg  this_program depend(array(Future) futures)
4d37ce2017-11-22Stephen R. van den Berg  {
d15f0c2017-11-24Stephen R. van den Berg  if (sizeof(futures)) { if (!astate) astate = ({(<>), ({})});
4d37ce2017-11-22Stephen R. van den Berg 
d15f0c2017-11-24Stephen R. van den Berg  int base = sizeof(astate[1]); astate[1] += allocate(sizeof(futures), UNDEFINED);
4d37ce2017-11-22Stephen R. van den Berg 
d15f0c2017-11-24Stephen R. van den Berg  futures->on_failure(try_failure);
4d37ce2017-11-22Stephen R. van den Berg 
d15f0c2017-11-24Stephen R. van den Berg  foreach(futures; int i; Future f) { int x = base + i; astate[0][x] = 1; f->on_success(depend_success, x, astate); }
4d37ce2017-11-22Stephen R. van den Berg  }
d15f0c2017-11-24Stephen R. van den Berg  return this;
4d37ce2017-11-22Stephen R. van den Berg  }
d15f0c2017-11-24Stephen R. van den Berg  inline variant this_program depend(Future ... futures)
4d37ce2017-11-22Stephen R. van den Berg  { return depend(futures); }
d15f0c2017-11-24Stephen R. van den Berg  variant this_program depend() { Promise p = Promise(); depend(p->future()); return p; }
4d37ce2017-11-22Stephen R. van den Berg 
d15f0c2017-11-24Stephen R. van den Berg  protected void depend_success(mixed value, int i, array astate)
4d37ce2017-11-22Stephen R. van den Berg  {
0d613e2017-11-23Stephen R. van den Berg  multiset pending = astate[0];
4d37ce2017-11-22Stephen R. van den Berg  if (state || !pending[i]) return; object key = mux->lock(); if (state || !pending[i]) return;
0d613e2017-11-23Stephen R. van den Berg  astate[1][i] = value;
4d37ce2017-11-22Stephen R. van den Berg  pending[i] = 0; if (sizeof(pending)) { key = 0; return; } key = 0;
0d613e2017-11-23Stephen R. van den Berg  success(astate[1]);
4d37ce2017-11-22Stephen R. van den Berg  }
d15f0c2017-11-24Stephen R. van den Berg  //! Add futures which the current object must accumulate. //! //! If called without arguments it will produce a new @[Future] //! from a new @[Promise] which is implictly added to the accumulation list.
0d613e2017-11-23Stephen R. van den Berg  //! //! @param futures //! The list of all the @expr{futures@} we must accumulate. //!
d15f0c2017-11-24Stephen R. van den Berg  //! @returns //! The new @[Promise]. //!
0d613e2017-11-23Stephen R. van den Berg  //! @seealso
c090d42017-11-23Stephen R. van den Berg  //! @[Concurrent.fold()], @[apply_fold()]
d15f0c2017-11-24Stephen R. van den Berg  this_program fold(array(Future) futures)
4d37ce2017-11-22Stephen R. van den Berg  {
0d613e2017-11-23Stephen R. van den Berg  if (!astate) astate = ({});
4d37ce2017-11-22Stephen R. van den Berg 
0d613e2017-11-23Stephen R. van den Berg  astate += futures;
d15f0c2017-11-24Stephen R. van den Berg  return this;
4d37ce2017-11-22Stephen R. van den Berg  }
d15f0c2017-11-24Stephen R. van den Berg  inline variant this_program fold(Future ... futures)
4d37ce2017-11-22Stephen R. van den Berg  { return fold(futures); }
d15f0c2017-11-24Stephen R. van den Berg  variant this_program fold() { Promise p = Promise(); fold(p->future()); return p; }
4d37ce2017-11-22Stephen R. van den Berg 
0d613e2017-11-23Stephen R. van den Berg  //! @param initial //! Initial value of the accumulator. //! //! @param fun //! Function to apply. The first argument is the result of
d15f0c2017-11-24Stephen R. van den Berg  //! 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].
0d613e2017-11-23Stephen R. van den Berg  //! //! @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. //! //! @seealso //! @[Concurrent.fold()], @[fold()]
d15f0c2017-11-24Stephen R. van den Berg  this_program apply_fold(mixed initial, function(mixed, mixed, mixed ... : mixed) fun, mixed ... extra)
4d37ce2017-11-22Stephen R. van den Berg  {
0d613e2017-11-23Stephen R. van den Berg  if (!astate || !sizeof(astate)) {
4d37ce2017-11-22Stephen R. van den Berg  success(initial);
d15f0c2017-11-24Stephen R. van den Berg  return this;
4d37ce2017-11-22Stephen R. van den Berg  }
0d613e2017-11-23Stephen R. van den Berg  array(Future) futures = astate; astate = 0;
4d37ce2017-11-22Stephen R. van den Berg  multiset pending = (<>); array astate = ({pending, initial});
d15f0c2017-11-24Stephen R. van den Berg  futures->on_failure(try_failure);
4d37ce2017-11-22Stephen R. van den Berg  foreach(futures; int i; Future f) { pending[i] = 1; f->on_success(fold_success, i, astate, fun, @extra); }
d15f0c2017-11-24Stephen R. van den Berg  return this;
4d37ce2017-11-22Stephen R. van den Berg  }
d15f0c2017-11-24Stephen R. van den Berg  protected void fold_success(mixed val, int i, array astate, function(mixed, mixed, mixed ... : mixed) fun, mixed ... extra)
4d37ce2017-11-22Stephen R. van den Berg  { multiset pending = astate[0]; if (state || !pending[i]) return; object key = mux->lock(); if (state || !pending[i]) return; pending[i] = 0; mixed err = catch { // FIXME: What if fun triggers a recursive call? astate[1] = fun(val, astate[1], @extra); if (sizeof(pending)) { key = 0; return; } }; key = 0;
d15f0c2017-11-24Stephen R. van den Berg  if (err) failure(err); else success(astate[1]);
4d37ce2017-11-22Stephen R. van den Berg  }
c071bc2017-11-05Henrik Grubbström (Grubba)  protected void _destruct()
9654f72016-04-12Henrik Grubbström (Grubba)  { if (!state) {
31dd442016-06-15Henrik Grubbström (Grubba)  unlocked_failure(({ "Promise broken.\n", backtrace() }));
9654f72016-04-12Henrik Grubbström (Grubba)  } } }
aa51262016-04-18Henrik Grubbström (Grubba)  protected class FirstCompleted { inherit Promise; protected void create(array(Future) futures) { if (!sizeof(futures)) { state = STATE_FULFILLED; return; }
82356b2016-10-26Henrik Grubbström (Grubba)  futures->on_failure(try_failure); futures->on_success(try_success);
aa51262016-04-18Henrik Grubbström (Grubba)  } }
6b01882016-11-09Stephen R. van den Berg //! @returns //! A @[Future] that represents the first //! of the @expr{futures@} that completes. //! //! @seealso //! @[race()]
1d407b2016-11-18Stephen R. van den Berg variant Future first_completed(array(Future) futures)
aa51262016-04-18Henrik Grubbström (Grubba) { Promise p = FirstCompleted(futures); return p->future(); }
1d407b2016-11-18Stephen R. van den Berg variant inline Future first_completed(Future ... futures) { return first_completed(futures); }
aa51262016-04-18Henrik Grubbström (Grubba) 
6e27292016-11-18Stephen R. van den Berg //! JavaScript Promise API equivalent of @[first_completed()].
6b01882016-11-09Stephen R. van den Berg //! //! @seealso //! @[first_completed()] //! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@}
1d407b2016-11-18Stephen R. van den Berg variant inline Future race(array(Future) futures) { return first_completed(futures); } variant inline Future race(Future ... futures)
6b01882016-11-09Stephen R. van den Berg { return first_completed(futures); } //! @returns //! A @[Future] that represents the array of all the completed @expr{futures@}. //! //! @seealso //! @[all()]
1d407b2016-11-18Stephen R. van den Berg variant Future results(array(Future) futures)
aa51262016-04-18Henrik Grubbström (Grubba) {
4d37ce2017-11-22Stephen R. van den Berg  Promise p = Promise(); p->depend(futures);
aa51262016-04-18Henrik Grubbström (Grubba)  return p->future(); }
1d407b2016-11-18Stephen R. van den Berg inline variant Future results(Future ... futures) { return results(futures); }
aa51262016-04-18Henrik Grubbström (Grubba) 
6b01882016-11-09Stephen R. van den Berg //! JavaScript Promise API equivalent of @[results()]. //! //! @seealso //! @[results()] //! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@}
6e27292016-11-18Stephen R. van den Berg inline variant Future all(array(Future) futures) { return results(futures); } inline variant Future all(Future ... futures)
6b01882016-11-09Stephen R. van den Berg { 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) { object p = Promise(); p->failure(reason);
1d407b2016-11-18Stephen R. van den Berg  return p->future();
6b01882016-11-09Stephen R. van den Berg } //! @returns //! A new @[Future] that has already been fulfilled with @expr{value@} //! as result. If @expr{value@} is an object which already
6e27292016-11-18Stephen R. van den Berg //! has @[on_failure] and @[on_success] methods, return it unchanged.
6b01882016-11-09Stephen R. van den Berg //! //! @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) {
6e27292016-11-18Stephen R. van den Berg  if (objectp(value) && value->on_failure && value->on_success)
6b01882016-11-09Stephen R. van den Berg  return value; object p = Promise(); p->success(value);
1d407b2016-11-18Stephen R. van den Berg  return p->future();
6b01882016-11-09Stephen R. van den Berg }
aa51262016-04-18Henrik Grubbström (Grubba) //! Return a @[Future] that represents the array of mapping @[fun]
6b01882016-11-09Stephen R. van den Berg //! over the results of the completed @[futures].
aa51262016-04-18Henrik Grubbström (Grubba) 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
8e14552016-10-26Henrik Grubbström (Grubba) //! 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.
aa51262016-04-18Henrik Grubbström (Grubba) Future fold(array(Future) futures, mixed initial, function(mixed, mixed, mixed ... : mixed) fun, mixed ... extra) {
4d37ce2017-11-22Stephen R. van den Berg  Promise p = Promise(); p->fold(futures);
c090d42017-11-23Stephen R. van den Berg  p->apply_fold(initial, fun, extra);
aa51262016-04-18Henrik Grubbström (Grubba)  return p->future(); }