Promise.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. using ZenFulcrum.EmbeddedBrowser.Promises;
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Text;
  7. namespace ZenFulcrum.EmbeddedBrowser
  8. {
  9. /// <summary>
  10. /// Implements a C# promise.
  11. /// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
  12. ///
  13. /// This can also be waited on in a Unity coroutine and queried for its value.
  14. /// </summary>
  15. public interface IPromise<PromisedT>
  16. {
  17. /// <summary>
  18. /// Set the name of the promise, useful for debugging.
  19. /// </summary>
  20. IPromise<PromisedT> WithName(string name);
  21. /// <summary>
  22. /// Completes the promise.
  23. /// onResolved is called on successful completion.
  24. /// onRejected is called on error.
  25. /// </summary>
  26. void Done(Action<PromisedT> onResolved, Action<Exception> onRejected);
  27. /// <summary>
  28. /// Completes the promise.
  29. /// onResolved is called on successful completion.
  30. /// Adds a default error handler.
  31. /// </summary>
  32. void Done(Action<PromisedT> onResolved);
  33. /// <summary>
  34. /// Complete the promise. Adds a default error handler.
  35. /// </summary>
  36. void Done();
  37. /// <summary>
  38. /// Handle errors for the promise.
  39. /// </summary>
  40. IPromise<PromisedT> Catch(Action<Exception> onRejected);
  41. /// <summary>
  42. /// Add a resolved callback that chains a value promise (optionally converting to a different value type).
  43. /// </summary>
  44. IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, IPromise<ConvertedT>> onResolved);
  45. /// <summary>
  46. /// Add a resolved callback that chains a non-value promise.
  47. /// </summary>
  48. IPromise Then(Func<PromisedT, IPromise> onResolved);
  49. /// <summary>
  50. /// Add a resolved callback.
  51. /// </summary>
  52. IPromise<PromisedT> Then(Action<PromisedT> onResolved);
  53. /// <summary>
  54. /// Add a resolved callback and a rejected callback.
  55. /// The resolved callback chains a value promise (optionally converting to a different value type).
  56. /// </summary>
  57. IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, IPromise<ConvertedT>> onResolved, Action<Exception> onRejected);
  58. /// <summary>
  59. /// Add a resolved callback and a rejected callback.
  60. /// The resolved callback chains a non-value promise.
  61. /// </summary>
  62. IPromise Then(Func<PromisedT, IPromise> onResolved, Action<Exception> onRejected);
  63. /// <summary>
  64. /// Add a resolved callback and a rejected callback.
  65. /// </summary>
  66. IPromise<PromisedT> Then(Action<PromisedT> onResolved, Action<Exception> onRejected);
  67. /// <summary>
  68. /// Return a new promise with a different value.
  69. /// May also change the type of the value.
  70. /// </summary>
  71. IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, ConvertedT> transform);
  72. /// <summary>
  73. /// Return a new promise with a different value.
  74. /// May also change the type of the value.
  75. /// </summary>
  76. [Obsolete("Use Then instead")]
  77. IPromise<ConvertedT> Transform<ConvertedT>(Func<PromisedT, ConvertedT> transform);
  78. /// <summary>
  79. /// Chain an enumerable of promises, all of which must resolve.
  80. /// Returns a promise for a collection of the resolved results.
  81. /// The resulting promise is resolved when all of the promises have resolved.
  82. /// It is rejected as soon as any of the promises have been rejected.
  83. /// </summary>
  84. IPromise<IEnumerable<ConvertedT>> ThenAll<ConvertedT>(Func<PromisedT, IEnumerable<IPromise<ConvertedT>>> chain);
  85. /// <summary>
  86. /// Chain an enumerable of promises, all of which must resolve.
  87. /// Converts to a non-value promise.
  88. /// The resulting promise is resolved when all of the promises have resolved.
  89. /// It is rejected as soon as any of the promises have been rejected.
  90. /// </summary>
  91. IPromise ThenAll(Func<PromisedT, IEnumerable<IPromise>> chain);
  92. /// <summary>
  93. /// Takes a function that yields an enumerable of promises.
  94. /// Returns a promise that resolves when the first of the promises has resolved.
  95. /// Yields the value from the first promise that has resolved.
  96. /// </summary>
  97. IPromise<ConvertedT> ThenRace<ConvertedT>(Func<PromisedT, IEnumerable<IPromise<ConvertedT>>> chain);
  98. /// <summary>
  99. /// Takes a function that yields an enumerable of promises.
  100. /// Converts to a non-value promise.
  101. /// Returns a promise that resolves when the first of the promises has resolved.
  102. /// Yields the value from the first promise that has resolved.
  103. /// </summary>
  104. IPromise ThenRace(Func<PromisedT, IEnumerable<IPromise>> chain);
  105. /// <summary>
  106. /// Returns the resulting value if resolved.
  107. /// Throws the rejection if rejected.
  108. /// Throws an exception if not settled.
  109. /// </summary>
  110. PromisedT Value { get; }
  111. /// <summary>
  112. /// Returns an enumerable that yields null until the promise is settled.
  113. /// ("To WaitFor" like the WaitForXXYY functions Unity provides.)
  114. /// Suitable for use with a Unity coroutine's "yield return promise.ToWaitFor()"
  115. /// Once it finishes, use promise.Value to retrieve the value/error.
  116. ///
  117. /// If throwOnFail is true, the coroutine will abort on promise rejection.
  118. /// </summary>
  119. /// <returns></returns>
  120. IEnumerator ToWaitFor(bool abortOnFail = false);
  121. }
  122. /// <summary>
  123. /// Interface for a promise that can be rejected.
  124. /// </summary>
  125. public interface IRejectable
  126. {
  127. /// <summary>
  128. /// Reject the promise with an exception.
  129. /// </summary>
  130. void Reject(Exception ex);
  131. }
  132. /// <summary>
  133. /// Interface for a promise that can be rejected or resolved.
  134. /// </summary>
  135. public interface IPendingPromise<PromisedT> : IRejectable
  136. {
  137. /// <summary>
  138. /// Resolve the promise with a particular value.
  139. /// </summary>
  140. void Resolve(PromisedT value);
  141. }
  142. /// <summary>
  143. /// Specifies the state of a promise.
  144. /// </summary>
  145. public enum PromiseState
  146. {
  147. Pending, // The promise is in-flight.
  148. Rejected, // The promise has been rejected.
  149. Resolved // The promise has been resolved.
  150. };
  151. /// <summary>
  152. /// Implements a C# promise.
  153. /// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
  154. /// </summary>
  155. public class Promise<PromisedT> : IPromise<PromisedT>, IPendingPromise<PromisedT>, IPromiseInfo
  156. {
  157. /// <summary>
  158. /// The exception when the promise is rejected.
  159. /// </summary>
  160. private Exception rejectionException;
  161. /// <summary>
  162. /// The value when the promises is resolved.
  163. /// </summary>
  164. private PromisedT resolveValue;
  165. /// <summary>
  166. /// Error handler.
  167. /// </summary>
  168. private List<RejectHandler> rejectHandlers;
  169. /// <summary>
  170. /// Completed handlers that accept a value.
  171. /// </summary>
  172. private List<Action<PromisedT>> resolveCallbacks;
  173. private List<IRejectable> resolveRejectables;
  174. /// <summary>
  175. /// ID of the promise, useful for debugging.
  176. /// </summary>
  177. public int Id { get; private set; }
  178. /// <summary>
  179. /// Name of the promise, when set, useful for debugging.
  180. /// </summary>
  181. public string Name { get; private set; }
  182. /// <summary>
  183. /// Tracks the current state of the promise.
  184. /// </summary>
  185. public PromiseState CurState { get; private set; }
  186. public Promise()
  187. {
  188. this.CurState = PromiseState.Pending;
  189. this.Id = ++Promise.nextPromiseId;
  190. if (Promise.EnablePromiseTracking)
  191. {
  192. Promise.pendingPromises.Add(this);
  193. }
  194. }
  195. public Promise(Action<Action<PromisedT>, Action<Exception>> resolver)
  196. {
  197. this.CurState = PromiseState.Pending;
  198. this.Id = ++Promise.nextPromiseId;
  199. if (Promise.EnablePromiseTracking)
  200. {
  201. Promise.pendingPromises.Add(this);
  202. }
  203. try
  204. {
  205. resolver(
  206. // Resolve
  207. value => Resolve(value),
  208. // Reject
  209. ex => Reject(ex)
  210. );
  211. }
  212. catch (Exception ex)
  213. {
  214. Reject(ex);
  215. }
  216. }
  217. /// <summary>
  218. /// Add a rejection handler for this promise.
  219. /// </summary>
  220. private void AddRejectHandler(Action<Exception> onRejected, IRejectable rejectable)
  221. {
  222. if (rejectHandlers == null)
  223. {
  224. rejectHandlers = new List<RejectHandler>();
  225. }
  226. rejectHandlers.Add(new RejectHandler() { callback = onRejected, rejectable = rejectable }); ;
  227. }
  228. /// <summary>
  229. /// Add a resolve handler for this promise.
  230. /// </summary>
  231. private void AddResolveHandler(Action<PromisedT> onResolved, IRejectable rejectable)
  232. {
  233. if (resolveCallbacks == null)
  234. {
  235. resolveCallbacks = new List<Action<PromisedT>>();
  236. }
  237. if (resolveRejectables == null)
  238. {
  239. resolveRejectables = new List<IRejectable>();
  240. }
  241. resolveCallbacks.Add(onResolved);
  242. resolveRejectables.Add(rejectable);
  243. }
  244. /// <summary>
  245. /// Invoke a single handler.
  246. /// </summary>
  247. private void InvokeHandler<T>(Action<T> callback, IRejectable rejectable, T value)
  248. {
  249. // Argument.NotNull(() => callback);
  250. // Argument.NotNull(() => rejectable);
  251. try
  252. {
  253. callback(value);
  254. }
  255. catch (Exception ex)
  256. {
  257. rejectable.Reject(ex);
  258. }
  259. }
  260. /// <summary>
  261. /// Helper function clear out all handlers after resolution or rejection.
  262. /// </summary>
  263. private void ClearHandlers()
  264. {
  265. rejectHandlers = null;
  266. resolveCallbacks = null;
  267. resolveRejectables = null;
  268. }
  269. /// <summary>
  270. /// Invoke all reject handlers.
  271. /// </summary>
  272. private void InvokeRejectHandlers(Exception ex)
  273. {
  274. // Argument.NotNull(() => ex);
  275. if (rejectHandlers != null)
  276. {
  277. rejectHandlers.Each(handler => InvokeHandler(handler.callback, handler.rejectable, ex));
  278. }
  279. ClearHandlers();
  280. }
  281. /// <summary>
  282. /// Invoke all resolve handlers.
  283. /// </summary>
  284. private void InvokeResolveHandlers(PromisedT value)
  285. {
  286. if (resolveCallbacks != null)
  287. {
  288. for (int i = 0, maxI = resolveCallbacks.Count; i < maxI; i++) {
  289. InvokeHandler(resolveCallbacks[i], resolveRejectables[i], value);
  290. }
  291. }
  292. ClearHandlers();
  293. }
  294. /// <summary>
  295. /// Reject the promise with an exception.
  296. /// </summary>
  297. public void Reject(Exception ex)
  298. {
  299. // Argument.NotNull(() => ex);
  300. if (CurState != PromiseState.Pending)
  301. {
  302. throw new ApplicationException("Attempt to reject a promise that is already in state: " + CurState + ", a promise can only be rejected when it is still in state: " + PromiseState.Pending);
  303. }
  304. rejectionException = ex;
  305. CurState = PromiseState.Rejected;
  306. if (Promise.EnablePromiseTracking)
  307. {
  308. Promise.pendingPromises.Remove(this);
  309. }
  310. InvokeRejectHandlers(ex);
  311. }
  312. /// <summary>
  313. /// Resolve the promise with a particular value.
  314. /// </summary>
  315. public void Resolve(PromisedT value)
  316. {
  317. if (CurState != PromiseState.Pending)
  318. {
  319. throw new ApplicationException("Attempt to resolve a promise that is already in state: " + CurState + ", a promise can only be resolved when it is still in state: " + PromiseState.Pending);
  320. }
  321. resolveValue = value;
  322. CurState = PromiseState.Resolved;
  323. if (Promise.EnablePromiseTracking)
  324. {
  325. Promise.pendingPromises.Remove(this);
  326. }
  327. InvokeResolveHandlers(value);
  328. }
  329. /// <summary>
  330. /// Completes the promise.
  331. /// onResolved is called on successful completion.
  332. /// onRejected is called on error.
  333. /// </summary>
  334. public void Done(Action<PromisedT> onResolved, Action<Exception> onRejected)
  335. {
  336. Then(onResolved, onRejected)
  337. .Catch(ex =>
  338. Promise.PropagateUnhandledException(this, ex)
  339. );
  340. }
  341. /// <summary>
  342. /// Completes the promise.
  343. /// onResolved is called on successful completion.
  344. /// Adds a default error handler.
  345. /// </summary>
  346. public void Done(Action<PromisedT> onResolved)
  347. {
  348. Then(onResolved)
  349. .Catch(ex =>
  350. Promise.PropagateUnhandledException(this, ex)
  351. );
  352. }
  353. /// <summary>
  354. /// Complete the promise. Adds a default error handler.
  355. /// </summary>
  356. public void Done()
  357. {
  358. Catch(ex =>
  359. Promise.PropagateUnhandledException(this, ex)
  360. );
  361. }
  362. /// <summary>
  363. /// Set the name of the promise, useful for debugging.
  364. /// </summary>
  365. public IPromise<PromisedT> WithName(string name)
  366. {
  367. this.Name = name;
  368. return this;
  369. }
  370. /// <summary>
  371. /// Handle errors for the promise.
  372. /// </summary>
  373. public IPromise<PromisedT> Catch(Action<Exception> onRejected)
  374. {
  375. // Argument.NotNull(() => onRejected);
  376. var resultPromise = new Promise<PromisedT>();
  377. resultPromise.WithName(Name);
  378. Action<PromisedT> resolveHandler = v =>
  379. {
  380. resultPromise.Resolve(v);
  381. };
  382. Action<Exception> rejectHandler = ex =>
  383. {
  384. onRejected(ex);
  385. resultPromise.Reject(ex);
  386. };
  387. ActionHandlers(resultPromise, resolveHandler, rejectHandler);
  388. return resultPromise;
  389. }
  390. /// <summary>
  391. /// Add a resolved callback that chains a value promise (optionally converting to a different value type).
  392. /// </summary>
  393. public IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, IPromise<ConvertedT>> onResolved)
  394. {
  395. return Then(onResolved, null);
  396. }
  397. /// <summary>
  398. /// Add a resolved callback that chains a non-value promise.
  399. /// </summary>
  400. public IPromise Then(Func<PromisedT, IPromise> onResolved)
  401. {
  402. return Then(onResolved, null);
  403. }
  404. /// <summary>
  405. /// Add a resolved callback.
  406. /// </summary>
  407. public IPromise<PromisedT> Then(Action<PromisedT> onResolved)
  408. {
  409. return Then(onResolved, null);
  410. }
  411. /// <summary>
  412. /// Add a resolved callback and a rejected callback.
  413. /// The resolved callback chains a value promise (optionally converting to a different value type).
  414. /// </summary>
  415. public IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, IPromise<ConvertedT>> onResolved, Action<Exception> onRejected)
  416. {
  417. // This version of the function must supply an onResolved.
  418. // Otherwise there is now way to get the converted value to pass to the resulting promise.
  419. // Argument.NotNull(() => onResolved);
  420. var resultPromise = new Promise<ConvertedT>();
  421. resultPromise.WithName(Name);
  422. Action<PromisedT> resolveHandler = v =>
  423. {
  424. onResolved(v)
  425. .Then(
  426. // Should not be necessary to specify the arg type on the next line, but Unity (mono) has an internal compiler error otherwise.
  427. (ConvertedT chainedValue) => resultPromise.Resolve(chainedValue),
  428. ex => resultPromise.Reject(ex)
  429. );
  430. };
  431. Action<Exception> rejectHandler = ex =>
  432. {
  433. if (onRejected != null)
  434. {
  435. onRejected(ex);
  436. }
  437. resultPromise.Reject(ex);
  438. };
  439. ActionHandlers(resultPromise, resolveHandler, rejectHandler);
  440. return resultPromise;
  441. }
  442. /// <summary>
  443. /// Add a resolved callback and a rejected callback.
  444. /// The resolved callback chains a non-value promise.
  445. /// </summary>
  446. public IPromise Then(Func<PromisedT, IPromise> onResolved, Action<Exception> onRejected)
  447. {
  448. var resultPromise = new Promise();
  449. resultPromise.WithName(Name);
  450. Action<PromisedT> resolveHandler = v =>
  451. {
  452. if (onResolved != null)
  453. {
  454. onResolved(v)
  455. .Then(
  456. () => resultPromise.Resolve(),
  457. ex => resultPromise.Reject(ex)
  458. );
  459. }
  460. else
  461. {
  462. resultPromise.Resolve();
  463. }
  464. };
  465. Action<Exception> rejectHandler = ex =>
  466. {
  467. if (onRejected != null)
  468. {
  469. onRejected(ex);
  470. }
  471. resultPromise.Reject(ex);
  472. };
  473. ActionHandlers(resultPromise, resolveHandler, rejectHandler);
  474. return resultPromise;
  475. }
  476. /// <summary>
  477. /// Add a resolved callback and a rejected callback.
  478. /// </summary>
  479. public IPromise<PromisedT> Then(Action<PromisedT> onResolved, Action<Exception> onRejected)
  480. {
  481. var resultPromise = new Promise<PromisedT>();
  482. resultPromise.WithName(Name);
  483. Action<PromisedT> resolveHandler = v =>
  484. {
  485. if (onResolved != null)
  486. {
  487. onResolved(v);
  488. }
  489. resultPromise.Resolve(v);
  490. };
  491. Action<Exception> rejectHandler = ex =>
  492. {
  493. if (onRejected != null)
  494. {
  495. onRejected(ex);
  496. }
  497. resultPromise.Reject(ex);
  498. };
  499. ActionHandlers(resultPromise, resolveHandler, rejectHandler);
  500. return resultPromise;
  501. }
  502. /// <summary>
  503. /// Return a new promise with a different value.
  504. /// May also change the type of the value.
  505. /// </summary>
  506. public IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, ConvertedT> transform)
  507. {
  508. // Argument.NotNull(() => transform);
  509. return Then(value => Promise<ConvertedT>.Resolved(transform(value)));
  510. }
  511. /// <summary>
  512. /// Return a new promise with a different value.
  513. /// May also change the type of the value.
  514. /// </summary>
  515. [Obsolete("Use Then instead")]
  516. public IPromise<ConvertedT> Transform<ConvertedT>(Func<PromisedT, ConvertedT> transform)
  517. {
  518. // Argument.NotNull(() => transform);
  519. return Then(value => Promise<ConvertedT>.Resolved(transform(value)));
  520. }
  521. /// <summary>
  522. /// Helper function to invoke or register resolve/reject handlers.
  523. /// </summary>
  524. private void ActionHandlers(IRejectable resultPromise, Action<PromisedT> resolveHandler, Action<Exception> rejectHandler)
  525. {
  526. if (CurState == PromiseState.Resolved)
  527. {
  528. InvokeHandler(resolveHandler, resultPromise, resolveValue);
  529. }
  530. else if (CurState == PromiseState.Rejected)
  531. {
  532. InvokeHandler(rejectHandler, resultPromise, rejectionException);
  533. }
  534. else
  535. {
  536. AddResolveHandler(resolveHandler, resultPromise);
  537. AddRejectHandler(rejectHandler, resultPromise);
  538. }
  539. }
  540. /// <summary>
  541. /// Chain an enumerable of promises, all of which must resolve.
  542. /// Returns a promise for a collection of the resolved results.
  543. /// The resulting promise is resolved when all of the promises have resolved.
  544. /// It is rejected as soon as any of the promises have been rejected.
  545. /// </summary>
  546. public IPromise<IEnumerable<ConvertedT>> ThenAll<ConvertedT>(Func<PromisedT, IEnumerable<IPromise<ConvertedT>>> chain)
  547. {
  548. return Then(value => Promise<ConvertedT>.All(chain(value)));
  549. }
  550. /// <summary>
  551. /// Chain an enumerable of promises, all of which must resolve.
  552. /// Converts to a non-value promise.
  553. /// The resulting promise is resolved when all of the promises have resolved.
  554. /// It is rejected as soon as any of the promises have been rejected.
  555. /// </summary>
  556. public IPromise ThenAll(Func<PromisedT, IEnumerable<IPromise>> chain)
  557. {
  558. return Then(value => Promise.All(chain(value)));
  559. }
  560. /// <summary>
  561. /// Returns a promise that resolves when all of the promises in the enumerable argument have resolved.
  562. /// Returns a promise of a collection of the resolved results.
  563. /// </summary>
  564. public static IPromise<IEnumerable<PromisedT>> All(params IPromise<PromisedT>[] promises)
  565. {
  566. return All((IEnumerable<IPromise<PromisedT>>)promises); // Cast is required to force use of the other All function.
  567. }
  568. /// <summary>
  569. /// Returns a promise that resolves when all of the promises in the enumerable argument have resolved.
  570. /// Returns a promise of a collection of the resolved results.
  571. /// </summary>
  572. public static IPromise<IEnumerable<PromisedT>> All(IEnumerable<IPromise<PromisedT>> promises)
  573. {
  574. var promisesArray = promises.ToArray();
  575. if (promisesArray.Length == 0)
  576. {
  577. return Promise<IEnumerable<PromisedT>>.Resolved(EnumerableExt.Empty<PromisedT>());
  578. }
  579. var remainingCount = promisesArray.Length;
  580. var results = new PromisedT[remainingCount];
  581. var resultPromise = new Promise<IEnumerable<PromisedT>>();
  582. resultPromise.WithName("All");
  583. promisesArray.Each((promise, index) =>
  584. {
  585. promise
  586. .Catch(ex =>
  587. {
  588. if (resultPromise.CurState == PromiseState.Pending)
  589. {
  590. // If a promise errorred and the result promise is still pending, reject it.
  591. resultPromise.Reject(ex);
  592. }
  593. })
  594. .Then(result =>
  595. {
  596. results[index] = result;
  597. --remainingCount;
  598. if (remainingCount <= 0)
  599. {
  600. // This will never happen if any of the promises errorred.
  601. resultPromise.Resolve(results);
  602. }
  603. })
  604. .Done();
  605. });
  606. return resultPromise;
  607. }
  608. /// <summary>
  609. /// Takes a function that yields an enumerable of promises.
  610. /// Returns a promise that resolves when the first of the promises has resolved.
  611. /// Yields the value from the first promise that has resolved.
  612. /// </summary>
  613. public IPromise<ConvertedT> ThenRace<ConvertedT>(Func<PromisedT, IEnumerable<IPromise<ConvertedT>>> chain)
  614. {
  615. return Then(value => Promise<ConvertedT>.Race(chain(value)));
  616. }
  617. /// <summary>
  618. /// Takes a function that yields an enumerable of promises.
  619. /// Converts to a non-value promise.
  620. /// Returns a promise that resolves when the first of the promises has resolved.
  621. /// Yields the value from the first promise that has resolved.
  622. /// </summary>
  623. public IPromise ThenRace(Func<PromisedT, IEnumerable<IPromise>> chain)
  624. {
  625. return Then(value => Promise.Race(chain(value)));
  626. }
  627. public PromisedT Value
  628. {
  629. get
  630. {
  631. if (CurState == PromiseState.Pending) throw new InvalidOperationException("Promise not settled");
  632. else if (CurState == PromiseState.Rejected) throw rejectionException;
  633. return resolveValue;
  634. }
  635. }
  636. class Enumerated<T> : IEnumerator {
  637. private Promise<T> promise;
  638. private bool abortOnFail;
  639. public Enumerated(Promise<T> promise, bool abortOnFail) {
  640. this.promise = promise;
  641. this.abortOnFail = abortOnFail;
  642. }
  643. public bool MoveNext() {
  644. if (abortOnFail && promise.CurState == PromiseState.Rejected) {
  645. throw promise.rejectionException;
  646. }
  647. return promise.CurState == PromiseState.Pending;
  648. }
  649. public void Reset() { }
  650. public object Current { get { return null; } }
  651. }
  652. public IEnumerator ToWaitFor(bool abortOnFail) {
  653. var ret = new Enumerated<PromisedT>(this, abortOnFail);
  654. //someone will poll for completion, so act like we've been terminated
  655. Done(x => {}, ex => {});
  656. return ret;
  657. }
  658. /// <summary>
  659. /// Returns a promise that resolves when the first of the promises in the enumerable argument have resolved.
  660. /// Returns the value from the first promise that has resolved.
  661. /// </summary>
  662. public static IPromise<PromisedT> Race(params IPromise<PromisedT>[] promises)
  663. {
  664. return Race((IEnumerable<IPromise<PromisedT>>)promises); // Cast is required to force use of the other function.
  665. }
  666. /// <summary>
  667. /// Returns a promise that resolves when the first of the promises in the enumerable argument have resolved.
  668. /// Returns the value from the first promise that has resolved.
  669. /// </summary>
  670. public static IPromise<PromisedT> Race(IEnumerable<IPromise<PromisedT>> promises)
  671. {
  672. var promisesArray = promises.ToArray();
  673. if (promisesArray.Length == 0)
  674. {
  675. throw new ApplicationException("At least 1 input promise must be provided for Race");
  676. }
  677. var resultPromise = new Promise<PromisedT>();
  678. resultPromise.WithName("Race");
  679. promisesArray.Each((promise, index) =>
  680. {
  681. promise
  682. .Catch(ex =>
  683. {
  684. if (resultPromise.CurState == PromiseState.Pending)
  685. {
  686. // If a promise errorred and the result promise is still pending, reject it.
  687. resultPromise.Reject(ex);
  688. }
  689. })
  690. .Then(result =>
  691. {
  692. if (resultPromise.CurState == PromiseState.Pending)
  693. {
  694. resultPromise.Resolve(result);
  695. }
  696. })
  697. .Done();
  698. });
  699. return resultPromise;
  700. }
  701. /// <summary>
  702. /// Convert a simple value directly into a resolved promise.
  703. /// </summary>
  704. public static IPromise<PromisedT> Resolved(PromisedT promisedValue)
  705. {
  706. var promise = new Promise<PromisedT>();
  707. promise.Resolve(promisedValue);
  708. return promise;
  709. }
  710. /// <summary>
  711. /// Convert an exception directly into a rejected promise.
  712. /// </summary>
  713. public static IPromise<PromisedT> Rejected(Exception ex)
  714. {
  715. // Argument.NotNull(() => ex);
  716. var promise = new Promise<PromisedT>();
  717. promise.Reject(ex);
  718. return promise;
  719. }
  720. }
  721. }