Skip to content

Commit 824b714

Browse files
authored
Merge pull request Real-Serious-Games#99 from alonsohki/master
Added Promise<T>.First() to find the first promise that returns a value.
2 parents e6d5348 + ee5daac commit 824b714

File tree

4 files changed

+358
-64
lines changed

4 files changed

+358
-64
lines changed

Tests/PromiseProgressTests.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,33 @@ public void exception_is_thrown_for_progress_after_reject()
154154
Assert.Throws<PromiseStateException>(() => promise.ReportProgress(1f));
155155
}
156156

157+
[Fact]
158+
public void first_progress_is_averaged()
159+
{
160+
var promiseA = new Promise<int>();
161+
var promiseB = new Promise<int>();
162+
var promiseC = new Promise<int>();
163+
var promiseD = new Promise<int>();
164+
165+
int currentStep = 0;
166+
var expectedProgress = new[] { 0.25f, 0.50f, 0.75f, 1f };
167+
168+
Promise<int>.First(() => promiseA, () => promiseB, () => promiseC, () => promiseD)
169+
.Progress(progress =>
170+
{
171+
Assert.InRange(currentStep, 0, expectedProgress.Length - 1);
172+
Assert.Equal(expectedProgress[currentStep], progress);
173+
++currentStep;
174+
});
175+
176+
promiseA.Reject(null);
177+
promiseC.Reject(null);
178+
promiseB.Reject(null);
179+
promiseD.Reject(null);
180+
181+
Assert.Equal(expectedProgress.Length, currentStep);
182+
}
183+
157184
[Fact]
158185
public void all_progress_is_averaged()
159186
{
@@ -179,7 +206,6 @@ public void all_progress_is_averaged()
179206
promiseD.ReportProgress(1f);
180207

181208
Assert.Equal(expectedProgress.Length, currentStep);
182-
Assert.Equal(expectedProgress.Length, currentStep);
183209
}
184210

185211
[Fact]

Tests/PromiseTests.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,54 @@ public void then_handler_is_not_invoked_for_rejected_promise()
217217
promise.Reject(new Exception("Rejection!"));
218218
}
219219

220+
[Fact]
221+
public void chain_multiple_promises_using_first()
222+
{
223+
var promise = new Promise<int>();
224+
var chainedPromise1 = Promise<int>.Rejected(null);
225+
var chainedPromise2 = Promise<int>.Rejected(null);
226+
var chainedPromise3 = Promise<int>.Resolved(9001);
227+
228+
bool completed = false;
229+
230+
Promise<int>
231+
.First(() => chainedPromise1, () => chainedPromise2, () => chainedPromise3, () =>
232+
{
233+
Assert.True(false, "Didn't stop on the first resolved promise");
234+
return Promise<int>.Rejected(null);
235+
})
236+
.Then(result =>
237+
{
238+
Assert.Equal(9001, result);
239+
completed = true;
240+
})
241+
;
242+
243+
Assert.Equal(true, completed);
244+
}
245+
246+
[Fact]
247+
public void chain_multiple_rejected_promises_using_first()
248+
{
249+
var promise = new Promise<int>();
250+
var chainedPromise1 = Promise<int>.Rejected(new Exception("First chained promise"));
251+
var chainedPromise2 = Promise<int>.Rejected(new Exception("Second chained promise"));
252+
var chainedPromise3 = Promise<int>.Rejected(new Exception("Third chained promise"));
253+
254+
bool completed = false;
255+
256+
Promise<int>
257+
.First(() => chainedPromise1, () => chainedPromise2, () => chainedPromise3)
258+
.Catch(ex =>
259+
{
260+
Assert.Equal("Third chained promise", ex.Message);
261+
completed = true;
262+
})
263+
;
264+
265+
Assert.Equal(true, completed);
266+
}
267+
220268
[Fact]
221269
public void chain_multiple_promises_using_all()
222270
{

src/Promise.cs

Lines changed: 158 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,12 @@ public Promise(Action<Action<PromisedT>, Action<Exception>> resolver)
295295
}
296296
}
297297

298+
private Promise(PromiseState initialState)
299+
{
300+
CurState = initialState;
301+
id = Promise.NextId();
302+
}
303+
298304
/// <summary>
299305
/// Add a rejection handler for this promise.
300306
/// </summary>
@@ -378,7 +384,8 @@ private void InvokeRejectHandlers(Exception ex)
378384

379385
if (rejectHandlers != null)
380386
{
381-
rejectHandlers.Each(handler => InvokeHandler(handler.callback, handler.rejectable, ex));
387+
for (int i = 0, maxI = rejectHandlers.Count; i < maxI; ++i)
388+
InvokeHandler(rejectHandlers[i].callback, rejectHandlers[i].rejectable, ex);
382389
}
383390

384391
ClearHandlers();
@@ -406,7 +413,8 @@ private void InvokeProgressHandlers(float progress)
406413
{
407414
if (progressHandlers != null)
408415
{
409-
progressHandlers.Each(handler => InvokeHandler(handler.callback, handler.rejectable, progress));
416+
for (int i = 0, maxI = progressHandlers.Count; i < maxI; ++i)
417+
InvokeHandler(progressHandlers[i].callback, progressHandlers[i].rejectable, progress);
410418
}
411419
}
412420

@@ -510,6 +518,9 @@ public void Done(Action<PromisedT> onResolved)
510518
/// </summary>
511519
public void Done()
512520
{
521+
if (CurState == PromiseState.Resolved)
522+
return;
523+
513524
Catch(ex =>
514525
Promise.PropagateUnhandledException(this, ex)
515526
);
@@ -529,6 +540,11 @@ public IPromise<PromisedT> WithName(string name)
529540
/// </summary>
530541
public IPromise Catch(Action<Exception> onRejected)
531542
{
543+
if (CurState == PromiseState.Resolved)
544+
{
545+
return Promise.Resolved();
546+
}
547+
532548
var resultPromise = new Promise();
533549
resultPromise.WithName(Name);
534550

@@ -558,6 +574,11 @@ public IPromise Catch(Action<Exception> onRejected)
558574
/// </summary>
559575
public IPromise<PromisedT> Catch(Func<Exception, PromisedT> onRejected)
560576
{
577+
if (CurState == PromiseState.Resolved)
578+
{
579+
return this;
580+
}
581+
561582
var resultPromise = new Promise<PromisedT>();
562583
resultPromise.WithName(Name);
563584

@@ -645,9 +666,21 @@ public IPromise<ConvertedT> Then<ConvertedT>(
645666
Action<float> onProgress
646667
)
647668
{
669+
if (CurState == PromiseState.Resolved)
670+
{
671+
try
672+
{
673+
return onResolved(resolveValue);
674+
}
675+
catch (Exception ex)
676+
{
677+
return Promise<ConvertedT>.Rejected(ex);
678+
}
679+
}
680+
648681
// This version of the function must supply an onResolved.
649682
// Otherwise there is now way to get the converted value to pass to the resulting promise.
650-
// Argument.NotNull(() => onResolved);
683+
// Argument.NotNull(() => onResolved);
651684

652685
var resultPromise = new Promise<ConvertedT>();
653686
resultPromise.WithName(Name);
@@ -700,6 +733,18 @@ Action<float> onProgress
700733
/// </summary>
701734
public IPromise Then(Func<PromisedT, IPromise> onResolved, Action<Exception> onRejected, Action<float> onProgress)
702735
{
736+
if (CurState == PromiseState.Resolved)
737+
{
738+
try
739+
{
740+
return onResolved(resolveValue);
741+
}
742+
catch (Exception ex)
743+
{
744+
return Promise.Rejected(ex);
745+
}
746+
}
747+
703748
var resultPromise = new Promise();
704749
resultPromise.WithName(Name);
705750

@@ -720,15 +765,19 @@ public IPromise Then(Func<PromisedT, IPromise> onResolved, Action<Exception> onR
720765
}
721766
};
722767

723-
Action<Exception> rejectHandler = ex =>
768+
Action<Exception> rejectHandler;
769+
if (onRejected != null)
724770
{
725-
if (onRejected != null)
771+
rejectHandler = ex =>
726772
{
727773
onRejected(ex);
728-
}
729-
730-
resultPromise.Reject(ex);
731-
};
774+
resultPromise.Reject(ex);
775+
};
776+
}
777+
else
778+
{
779+
rejectHandler = resultPromise.Reject;
780+
}
732781

733782
ActionHandlers(resultPromise, resolveHandler, rejectHandler);
734783
if (onProgress != null)
@@ -744,6 +793,19 @@ public IPromise Then(Func<PromisedT, IPromise> onResolved, Action<Exception> onR
744793
/// </summary>
745794
public IPromise Then(Action<PromisedT> onResolved, Action<Exception> onRejected, Action<float> onProgress)
746795
{
796+
if (CurState == PromiseState.Resolved)
797+
{
798+
try
799+
{
800+
onResolved(resolveValue);
801+
return Promise.Resolved();
802+
}
803+
catch (Exception ex)
804+
{
805+
return Promise.Rejected(ex);
806+
}
807+
}
808+
747809
var resultPromise = new Promise();
748810
resultPromise.WithName(Name);
749811

@@ -757,15 +819,19 @@ public IPromise Then(Action<PromisedT> onResolved, Action<Exception> onRejected,
757819
resultPromise.Resolve();
758820
};
759821

760-
Action<Exception> rejectHandler = ex =>
822+
Action<Exception> rejectHandler;
823+
if (onRejected != null)
761824
{
762-
if (onRejected != null)
825+
rejectHandler = ex =>
763826
{
764827
onRejected(ex);
765-
}
766-
767-
resultPromise.Reject(ex);
768-
};
828+
resultPromise.Reject(ex);
829+
};
830+
}
831+
else
832+
{
833+
rejectHandler = resultPromise.Reject;
834+
}
769835

770836
ActionHandlers(resultPromise, resolveHandler, rejectHandler);
771837
if (onProgress != null)
@@ -817,6 +883,64 @@ private void ProgressHandlers(IRejectable resultPromise, Action<float> progressH
817883
}
818884
}
819885

886+
/// <summary>
887+
/// Chain a number of operations using promises.
888+
/// Returns the value of the first promise that resolves, or otherwise the exception thrown by the last operation.
889+
/// </summary>
890+
public static IPromise<T> First<T>(params Func<IPromise<T>>[] fns)
891+
{
892+
return First((IEnumerable<Func<IPromise<T>>>)fns);
893+
}
894+
895+
/// <summary>
896+
/// Chain a number of operations using promises.
897+
/// Returns the value of the first promise that resolves, or otherwise the exception thrown by the last operation.
898+
/// </summary>
899+
public static IPromise<T> First<T>(IEnumerable<Func<IPromise<T>>> fns)
900+
{
901+
var promise = new Promise<T>();
902+
903+
int count = 0;
904+
905+
fns.Aggregate(
906+
Promise<T>.Rejected(null),
907+
(prevPromise, fn) =>
908+
{
909+
int itemSequence = count;
910+
++count;
911+
912+
var newPromise = new Promise<T>();
913+
prevPromise
914+
.Progress(v =>
915+
{
916+
var sliceLength = 1f / count;
917+
promise.ReportProgress(sliceLength * (v + itemSequence));
918+
})
919+
.Then((Action<T>)newPromise.Resolve)
920+
.Catch(ex =>
921+
{
922+
var sliceLength = 1f / count;
923+
promise.ReportProgress(sliceLength * itemSequence);
924+
925+
fn()
926+
.Then(value => newPromise.Resolve(value))
927+
.Catch(newPromise.Reject)
928+
.Done()
929+
;
930+
})
931+
;
932+
return newPromise;
933+
})
934+
.Then(value => promise.Resolve(value))
935+
.Catch(ex =>
936+
{
937+
promise.ReportProgress(1f);
938+
promise.Reject(ex);
939+
});
940+
941+
return promise;
942+
}
943+
820944
/// <summary>
821945
/// Chain an enumerable of promises, all of which must resolve.
822946
/// Returns a promise for a collection of the resolved results.
@@ -989,8 +1113,8 @@ public static IPromise<PromisedT> Race(IEnumerable<IPromise<PromisedT>> promises
9891113
/// </summary>
9901114
public static IPromise<PromisedT> Resolved(PromisedT promisedValue)
9911115
{
992-
var promise = new Promise<PromisedT>();
993-
promise.Resolve(promisedValue);
1116+
var promise = new Promise<PromisedT>(PromiseState.Resolved);
1117+
promise.resolveValue = promisedValue;
9941118
return promise;
9951119
}
9961120

@@ -1001,17 +1125,30 @@ public static IPromise<PromisedT> Rejected(Exception ex)
10011125
{
10021126
// Argument.NotNull(() => ex);
10031127

1004-
var promise = new Promise<PromisedT>();
1005-
promise.Reject(ex);
1128+
var promise = new Promise<PromisedT>(PromiseState.Rejected);
1129+
promise.rejectionException = ex;
10061130
return promise;
10071131
}
10081132

10091133
public IPromise<PromisedT> Finally(Action onComplete)
10101134
{
1135+
if (CurState == PromiseState.Resolved)
1136+
{
1137+
try
1138+
{
1139+
onComplete();
1140+
return this;
1141+
}
1142+
catch (Exception ex)
1143+
{
1144+
return Rejected(ex);
1145+
}
1146+
}
1147+
10111148
var promise = new Promise<PromisedT>();
10121149
promise.WithName(Name);
10131150

1014-
this.Then(x => promise.Resolve(x));
1151+
this.Then((Action<PromisedT>)promise.Resolve);
10151152
this.Catch(e => {
10161153
try {
10171154
onComplete();
@@ -1052,7 +1189,7 @@ public IPromise<ConvertedT> ContinueWith<ConvertedT>(Func<IPromise<ConvertedT>>
10521189

10531190
public IPromise<PromisedT> Progress(Action<float> onProgress)
10541191
{
1055-
if (onProgress != null)
1192+
if (CurState == PromiseState.Pending && onProgress != null)
10561193
{
10571194
ProgressHandlers(this, onProgress);
10581195
}

0 commit comments

Comments
 (0)