├── .editorconfig ├── .gitattributes ├── .gitignore ├── .nuget ├── Microsoft.Build.dll ├── NuGet.Config ├── NuGet.exe ├── NuGet.mono.targets └── NuGet.targets ├── .travis.yml ├── C-Sharp-Promise.sln ├── CONTRIBUTING.md ├── Examples ├── Example1 │ ├── App.config │ ├── Example1.csproj │ ├── Program.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── Example2 │ ├── App.config │ ├── Example2.csproj │ ├── Program.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── Example3 │ ├── App.config │ ├── Example3.csproj │ ├── Program.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── Example4 │ ├── App.config │ ├── Example4.csproj │ ├── Program.cs │ └── Properties │ │ └── AssemblyInfo.cs └── Example5 │ ├── App.config │ ├── Example5.csproj │ ├── Program.cs │ └── Properties │ └── AssemblyInfo.cs ├── LICENSE ├── README.md ├── Tests ├── A+ Spec │ ├── 2.1.cs │ └── 2.2.cs ├── Promise.Tests.csproj ├── PromiseProgressTests.cs ├── PromiseTests.cs ├── PromiseTimerTests.cs ├── Promise_NonGeneric_ProgressTests.cs ├── Promise_NonGeneric_Tests.cs ├── Properties │ └── AssemblyInfo.cs ├── TestHelpers.cs └── packages.config ├── packages ├── Moq.4.2.1409.1722 │ ├── Moq.4.2.1409.1722.nupkg │ └── lib │ │ ├── net35 │ │ ├── Moq.dll │ │ └── Moq.xml │ │ ├── net40 │ │ ├── Moq.dll │ │ └── Moq.xml │ │ └── sl4 │ │ ├── Moq.Silverlight.dll │ │ └── Moq.Silverlight.xml ├── xunit.1.9.2 │ ├── lib │ │ └── net20 │ │ │ ├── xunit.dll │ │ │ ├── xunit.dll.tdnet │ │ │ ├── xunit.runner.msbuild.dll │ │ │ ├── xunit.runner.tdnet.dll │ │ │ ├── xunit.runner.utility.dll │ │ │ └── xunit.xml │ └── xunit.1.9.2.nupkg └── xunit.runner.visualstudio.0.99.9-build1021 │ ├── build │ ├── _common │ │ ├── xunit.abstractions.dll │ │ ├── xunit.runner.utility.dll │ │ ├── xunit.runner.visualstudio.testadapter.dll │ │ └── xunit.runner.visualstudio.testadapter.pdb │ ├── net20 │ │ └── xunit.runner.visualstudio.props │ └── portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid │ │ └── xunit.runner.visualstudio.props │ ├── lib │ ├── net20 │ │ └── _._ │ └── portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid │ │ └── _._ │ └── xunit.runner.visualstudio.0.99.9-build1021.nupkg ├── src ├── C-Sharp-Promise.snk ├── EnumerableExt.cs ├── Exceptions │ ├── PromiseException.cs │ └── PromiseStateException.cs ├── Promise.cs ├── PromiseHelpers.cs ├── PromiseTimer.cs ├── Promise_NonGeneric.cs ├── RSG.Promise.csproj └── Tuple.cs └── xunit ├── xunit.console.clr4.x86.exe └── xunit.runner.utility.dll /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = crlf 7 | insert_final_newline = false 8 | indent_style = space 9 | indent_size = 4 10 | charset = utf-8 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | .vs 4 | 5 | # User-specific files 6 | *.suo 7 | *.user 8 | *.sln.docstates 9 | 10 | # Build results 11 | 12 | [Dd]ebug/ 13 | [Rr]elease/ 14 | x64/ 15 | build/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 20 | !packages/*/build/ 21 | 22 | # MSTest test Results 23 | [Tt]est[Rr]esult*/ 24 | [Bb]uild[Ll]og.* 25 | 26 | *_i.c 27 | *_p.c 28 | *.ilk 29 | *.meta 30 | *.obj 31 | *.pch 32 | *.pdb 33 | *.pgc 34 | *.pgd 35 | *.rsp 36 | *.sbr 37 | *.tlb 38 | *.tli 39 | *.tlh 40 | *.tmp 41 | *.tmp_proj 42 | *.log 43 | *.vspscc 44 | *.vssscc 45 | .builds 46 | *.pidb 47 | *.log 48 | *.scc 49 | 50 | # Visual C++ cache files 51 | ipch/ 52 | *.aps 53 | *.ncb 54 | *.opensdf 55 | *.sdf 56 | *.cachefile 57 | 58 | # Visual Studio profiler 59 | *.psess 60 | *.vsp 61 | *.vspx 62 | 63 | # Guidance Automation Toolkit 64 | *.gpState 65 | 66 | # ReSharper is a .NET coding add-in 67 | _ReSharper*/ 68 | *.[Rr]e[Ss]harper 69 | 70 | # TeamCity is a build add-in 71 | _TeamCity* 72 | 73 | # DotCover is a Code Coverage Tool 74 | *.dotCover 75 | 76 | # NCrunch 77 | *.ncrunch* 78 | .*crunch*.local.xml 79 | 80 | # Installshield output folder 81 | [Ee]xpress/ 82 | 83 | # DocProject is a documentation generator add-in 84 | DocProject/buildhelp/ 85 | DocProject/Help/*.HxT 86 | DocProject/Help/*.HxC 87 | DocProject/Help/*.hhc 88 | DocProject/Help/*.hhk 89 | DocProject/Help/*.hhp 90 | DocProject/Help/Html2 91 | DocProject/Help/html 92 | 93 | # Click-Once directory 94 | publish/ 95 | 96 | # Publish Web Output 97 | *.Publish.xml 98 | 99 | # NuGet Packages Directory 100 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 101 | #packages/ 102 | 103 | # Windows Azure Build Output 104 | csx 105 | *.build.csdef 106 | 107 | # Windows Store app package directory 108 | AppPackages/ 109 | 110 | # Others 111 | sql/ 112 | *.Cache 113 | ClientBin/ 114 | [Ss]tyle[Cc]op.* 115 | ~$* 116 | *~ 117 | *.dbmdl 118 | *.[Pp]ublish.xml 119 | *.pfx 120 | *.publishsettings 121 | 122 | # RIA/Silverlight projects 123 | Generated_Code/ 124 | 125 | # Backup & report files from converting an old project file to a newer 126 | # Visual Studio version. Backup files are not needed, because we have git ;-) 127 | _UpgradeReport_Files/ 128 | Backup*/ 129 | UpgradeLog*.XML 130 | UpgradeLog*.htm 131 | 132 | # SQL Server files 133 | App_Data/*.mdf 134 | App_Data/*.ldf 135 | 136 | 137 | #LightSwitch generated files 138 | GeneratedArtifacts/ 139 | _Pvt_Extensions/ 140 | ModelManifest.xml 141 | 142 | #Ignore files generated by Rider IDE 143 | .idea/** 144 | 145 | # ========================= 146 | # Windows detritus 147 | # ========================= 148 | 149 | # Windows image file caches 150 | Thumbs.db 151 | ehthumbs.db 152 | 153 | # Folder config file 154 | Desktop.ini 155 | 156 | # Recycle Bin used on file shares 157 | $RECYCLE.BIN/ 158 | 159 | # Mac desktop service store files 160 | .DS_Store 161 | packages/**/*.nupkg 162 | packages/**/*.nuspec 163 | *.nupkg 164 | packages/ -------------------------------------------------------------------------------- /.nuget/Microsoft.Build.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/.nuget/Microsoft.Build.dll -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/.nuget/NuGet.exe -------------------------------------------------------------------------------- /.nuget/NuGet.mono.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 "$(NuGetExePath)" 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir)" 62 | "$(SolutionDir)" 63 | 64 | 65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 67 | 68 | 69 | 70 | RestorePackages; 71 | $(BuildDependsOn); 72 | 73 | 74 | 75 | 76 | $(BuildDependsOn); 77 | BuildPackage; 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 "$(NuGetExePath)" 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir) " 62 | "$(SolutionDir)" 63 | 64 | 65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 67 | 68 | 69 | 70 | RestorePackages; 71 | $(BuildDependsOn); 72 | 73 | 74 | 75 | 76 | $(BuildDependsOn); 77 | BuildPackage; 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | solution: C-Sharp-Promise.sln 3 | 4 | install: 5 | - nuget restore C-Sharp-Promise.sln 6 | - nuget install xunit.runners -Version 1.9.2 -OutputDirectory testrunner 7 | 8 | script: 9 | - msbuild /p:Configuration=Debug C-Sharp-Promise.sln 10 | - mono ./testrunner/xunit.runners.1.9.2/tools/xunit.console.exe ./bin/Debug/RSG.Promise.Tests.dll 11 | -------------------------------------------------------------------------------- /C-Sharp-Promise.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2042 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSG.Promise", "src\RSG.Promise.csproj", "{7E1C6A8A-84E7-43C3-AE73-C6E8A9F11AF3}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Promise.Tests", "Tests\Promise.Tests.csproj", "{4B4EE1A6-25A3-43D0-BAE9-5E45E31F2816}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{79A9B3AC-1942-4441-872C-41CF7BB240A4}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example1", "Examples\Example1\Example1.csproj", "{79A792E9-F378-47BB-A262-9E4E33B655DA}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example2", "Examples\Example2\Example2.csproj", "{D98539EC-14C7-40B0-9ADA-D8378A1774C4}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example3", "Examples\Example3\Example3.csproj", "{6E70ADB8-B34B-4C71-BE15-3001F5C791B8}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example4", "Examples\Example4\Example4.csproj", "{BDB0135C-B075-47BE-A971-1927BEA6F622}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example5", "Examples\Example5\Example5.csproj", "{1060401E-1C17-4741-BF1E-9F05B5427775}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {7E1C6A8A-84E7-43C3-AE73-C6E8A9F11AF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {7E1C6A8A-84E7-43C3-AE73-C6E8A9F11AF3}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {7E1C6A8A-84E7-43C3-AE73-C6E8A9F11AF3}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {7E1C6A8A-84E7-43C3-AE73-C6E8A9F11AF3}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {4B4EE1A6-25A3-43D0-BAE9-5E45E31F2816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {4B4EE1A6-25A3-43D0-BAE9-5E45E31F2816}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {4B4EE1A6-25A3-43D0-BAE9-5E45E31F2816}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {4B4EE1A6-25A3-43D0-BAE9-5E45E31F2816}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {79A792E9-F378-47BB-A262-9E4E33B655DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {79A792E9-F378-47BB-A262-9E4E33B655DA}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {79A792E9-F378-47BB-A262-9E4E33B655DA}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {79A792E9-F378-47BB-A262-9E4E33B655DA}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {D98539EC-14C7-40B0-9ADA-D8378A1774C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {D98539EC-14C7-40B0-9ADA-D8378A1774C4}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {D98539EC-14C7-40B0-9ADA-D8378A1774C4}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {D98539EC-14C7-40B0-9ADA-D8378A1774C4}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {6E70ADB8-B34B-4C71-BE15-3001F5C791B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {6E70ADB8-B34B-4C71-BE15-3001F5C791B8}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {6E70ADB8-B34B-4C71-BE15-3001F5C791B8}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {6E70ADB8-B34B-4C71-BE15-3001F5C791B8}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {BDB0135C-B075-47BE-A971-1927BEA6F622}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {BDB0135C-B075-47BE-A971-1927BEA6F622}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {BDB0135C-B075-47BE-A971-1927BEA6F622}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {BDB0135C-B075-47BE-A971-1927BEA6F622}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {1060401E-1C17-4741-BF1E-9F05B5427775}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {1060401E-1C17-4741-BF1E-9F05B5427775}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {1060401E-1C17-4741-BF1E-9F05B5427775}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {1060401E-1C17-4741-BF1E-9F05B5427775}.Release|Any CPU.Build.0 = Release|Any CPU 56 | EndGlobalSection 57 | GlobalSection(SolutionProperties) = preSolution 58 | HideSolutionNode = FALSE 59 | EndGlobalSection 60 | GlobalSection(NestedProjects) = preSolution 61 | {79A792E9-F378-47BB-A262-9E4E33B655DA} = {79A9B3AC-1942-4441-872C-41CF7BB240A4} 62 | {D98539EC-14C7-40B0-9ADA-D8378A1774C4} = {79A9B3AC-1942-4441-872C-41CF7BB240A4} 63 | {6E70ADB8-B34B-4C71-BE15-3001F5C791B8} = {79A9B3AC-1942-4441-872C-41CF7BB240A4} 64 | {BDB0135C-B075-47BE-A971-1927BEA6F622} = {79A9B3AC-1942-4441-872C-41CF7BB240A4} 65 | {1060401E-1C17-4741-BF1E-9F05B5427775} = {79A9B3AC-1942-4441-872C-41CF7BB240A4} 66 | EndGlobalSection 67 | GlobalSection(ExtensibilityGlobals) = postSolution 68 | SolutionGuid = {7D2FBA48-64F2-4236-BA93-0AF605D743D8} 69 | EndGlobalSection 70 | EndGlobal 71 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute to C-Sharp-Promise 2 | 3 | 👍🎉 First off, thanks for taking the time to contribute! 🎉👍 4 | 5 | 6 | ## Reporting bugs 7 | 8 | Any bug reports are useful, although there are a few things you can do that will 9 | make it easier for maintainers and other users to understand your report, 10 | reproduce the behaviour, and find related reports. 11 | 12 | - **Use a clear and descriptive title** for the issue to identify the problem. 13 | - **Describe the exact steps which reproduce the problem** in as many details 14 | as possible. Code examples or links to repos to reproduce the issue are 15 | always helpful. 16 | - **Describe the behaviour you observed after following the steps** and point 17 | out what exactly the problem is with that behaviour. 18 | - **Explain which behaviour you expected to see instead and why.** Since 19 | C-Sharp-Promise is designed to replicate [JavaScript promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises), 20 | code that works with JavaScript promises but gives a different result or 21 | breaks with C-Sharp-Promise would be a a good example. 22 | - **Check the existing open issues on GitHub** to see if someone else has had 23 | the same problem as you. 24 | 25 | Make sure to file bugs as [GitHub issues](https://github.com/Real-Serious-Games/C-Sharp-Promise/issues), 26 | since that way everyone working on the library can see it and potentially help. 27 | 28 | 29 | ## Pull requests 30 | 31 | Before we merge pull requests there are a few things we look for to make sure 32 | the code is maintainable and up the same standard as the rest of the library. 33 | 34 | - Make sure you've written comprehensive unit tests for the feature, or 35 | modify existing tests if the feature changes functionality. 36 | - Check that your code conforms to the same style as existing code. Ensure that 37 | your editor is set up to read the [.editorconfig](http://editorconfig.org/) 38 | file for consistent spacing and line endings. We also try to keep our code 39 | style consistent with the [Microsoft C# Coding Conventions](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions) 40 | and [Framework Design Guidelines](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/index). 41 | - Make sure that the [Travis CI build](https://travis-ci.org/Real-Serious-Games/C-Sharp-Promise) 42 | succeeds. This should run automatically when you create a pull request, but 43 | should have the same result as building the solution and running all the 44 | tests locally. We will not accept any pull requests that fail to build or 45 | contain failing tests. 46 | - If you have added a new feature, add a section to README.md describing the 47 | feature and how to use it. 48 | 49 | In addition, if your pull request breaks any existing functionality it's 50 | unlikely we will merge it unless there is a very good reason. -------------------------------------------------------------------------------- /Examples/Example1/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Examples/Example1/Example1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {79A792E9-F378-47BB-A262-9E4E33B655DA} 8 | Exe 9 | Properties 10 | Example 11 | Example 12 | v3.5 13 | 512 14 | 15 | ..\..\ 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {7e1c6a8a-84e7-43c3-ae73-c6e8a9f11af3} 55 | RSG.Promise 56 | 57 | 58 | 59 | 60 | 61 | 62 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /Examples/Example1/Program.cs: -------------------------------------------------------------------------------- 1 | using RSG; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading; 8 | 9 | // 10 | // Example of downloading text from a URL using a promise. 11 | // 12 | namespace Example 13 | { 14 | class Program 15 | { 16 | static void Main(string[] args) 17 | { 18 | var running = true; 19 | 20 | Download("http://www.google.com") // Schedule an async operation. 21 | .Then(result => // Use Done to register a callback to handle completion of the async operation. 22 | { 23 | Console.WriteLine("Async operation completed."); 24 | Console.WriteLine(result.Substring(0, 250) + "..."); 25 | running = false; 26 | }) 27 | .Done(); 28 | 29 | Console.WriteLine("Waiting"); 30 | 31 | while (running) 32 | { 33 | Thread.Sleep(10); 34 | } 35 | 36 | Console.WriteLine("Exiting"); 37 | } 38 | 39 | /// 40 | /// Download text from a URL. 41 | /// A promise is returned that is resolved when the download has completed. 42 | /// The promise is rejected if an error occurs during download. 43 | /// 44 | static IPromise Download(string url) 45 | { 46 | Console.WriteLine("Downloading " + url + " ..."); 47 | 48 | var promise = new Promise(); 49 | using (var client = new WebClient()) 50 | { 51 | client.DownloadStringCompleted += 52 | (s, ev) => 53 | { 54 | if (ev.Error != null) 55 | { 56 | Console.WriteLine("An error occurred... rejecting the promise."); 57 | 58 | // Error during download, reject the promise. 59 | promise.Reject(ev.Error); 60 | } 61 | else 62 | { 63 | Console.WriteLine("... Download completed."); 64 | 65 | // Downloaded completed successfully, resolve the promise. 66 | promise.Resolve(ev.Result); 67 | } 68 | }; 69 | 70 | client.DownloadStringAsync(new Uri(url), null); 71 | } 72 | return promise; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Examples/Example1/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Example")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Real Serious Games")] 12 | [assembly: AssemblyProduct("Example")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("44f124da-f58f-4c64-94a2-30acaeea3111")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Examples/Example2/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Examples/Example2/Example2.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D98539EC-14C7-40B0-9ADA-D8378A1774C4} 8 | Exe 9 | Properties 10 | Example 11 | Example 12 | v3.5 13 | 512 14 | 15 | ..\..\ 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {7e1c6a8a-84e7-43c3-ae73-c6e8a9f11af3} 55 | RSG.Promise 56 | 57 | 58 | 59 | 60 | 61 | 62 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /Examples/Example2/Program.cs: -------------------------------------------------------------------------------- 1 | using RSG; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading; 8 | 9 | // 10 | // Example of a promise that is rejected because of an error during the async operation. 11 | // 12 | namespace Example 13 | { 14 | class Program 15 | { 16 | static void Main(string[] args) 17 | { 18 | var running = true; 19 | 20 | Download("http://www.bugglebogglebazzooo.com") // Schedule async operation, this time the URL is bad! 21 | .Catch(exception => 22 | { 23 | Console.WriteLine("Async operation errorred."); 24 | Console.WriteLine(exception); 25 | running = false; 26 | }); 27 | 28 | Console.WriteLine("Waiting"); 29 | 30 | while (running) 31 | { 32 | Thread.Sleep(10); 33 | } 34 | 35 | Console.WriteLine("Exiting"); 36 | } 37 | 38 | /// 39 | /// Download text from a URL. 40 | /// A promise is returned that is resolved when the download has completed. 41 | /// The promise is rejected if an error occurs during download. 42 | /// 43 | static IPromise Download(string url) 44 | { 45 | Console.WriteLine("Downloading " + url + " ..."); 46 | 47 | var promise = new Promise(); 48 | using (var client = new WebClient()) 49 | { 50 | client.DownloadStringCompleted += 51 | (s, ev) => 52 | { 53 | if (ev.Error != null) 54 | { 55 | Console.WriteLine("An error occurred... rejecting the promise."); 56 | 57 | // Error during download, reject the promise. 58 | promise.Reject(ev.Error); 59 | } 60 | else 61 | { 62 | Console.WriteLine("... Download completed."); 63 | 64 | // Downloaded completed successfully, resolve the promise. 65 | promise.Resolve(ev.Result); 66 | } 67 | }; 68 | 69 | client.DownloadStringAsync(new Uri(url), null); 70 | } 71 | return promise; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Examples/Example2/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Example")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Real Serious Games")] 12 | [assembly: AssemblyProduct("Example")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("44f124da-f58f-4c64-94a2-30acaeea3111")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Examples/Example3/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Examples/Example3/Example3.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6E70ADB8-B34B-4C71-BE15-3001F5C791B8} 8 | Exe 9 | Properties 10 | Example 11 | Example 12 | v3.5 13 | 512 14 | 15 | ..\..\ 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {7e1c6a8a-84e7-43c3-ae73-c6e8a9f11af3} 55 | RSG.Promise 56 | 57 | 58 | 59 | 60 | 61 | 62 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /Examples/Example3/Program.cs: -------------------------------------------------------------------------------- 1 | using RSG; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Threading; 9 | 10 | // 11 | // This example downloads search results from google then transforms the result to extract links. 12 | // It includes both error handling and a completion handler. 13 | // 14 | namespace Example 15 | { 16 | class Program 17 | { 18 | // 19 | // URL for a google search on 'promises'. 20 | // 21 | static string searchUrl = "https://www.google.com/#q=promises"; 22 | 23 | static void Main(string[] args) 24 | { 25 | var running = true; 26 | 27 | Download(searchUrl) // Invoke a google search. 28 | .Then(html => // Transforms search results and extract links. 29 | { 30 | return LinkFinder 31 | .Find(html) 32 | .Select(link => link.Href) 33 | .ToArray(); 34 | }) 35 | .Then(links => // Display the links that were extracted. 36 | { 37 | Console.WriteLine("Async operation completed."); 38 | foreach (var link in links) 39 | { 40 | Console.WriteLine(link); 41 | } 42 | running = false; 43 | }) 44 | .Catch(exception => // Catch any errors that happen during download or transform. 45 | { 46 | Console.WriteLine("Async operation errorred."); 47 | Console.WriteLine(exception); 48 | running = false; 49 | }) 50 | .Done(); 51 | 52 | Console.WriteLine("Waiting"); 53 | 54 | while (running) 55 | { 56 | Thread.Sleep(10); 57 | } 58 | 59 | Console.WriteLine("Exiting"); 60 | } 61 | 62 | /// 63 | /// Download text from a URL. 64 | /// A promise is returned that is resolved when the download has completed. 65 | /// The promise is rejected if an error occurs during download. 66 | /// 67 | static IPromise Download(string url) 68 | { 69 | Console.WriteLine("Downloading " + url + " ..."); 70 | 71 | var promise = new Promise(); 72 | using (var client = new WebClient()) 73 | { 74 | client.DownloadStringCompleted += 75 | (s, ev) => 76 | { 77 | if (ev.Error != null) 78 | { 79 | Console.WriteLine("An error occurred... rejecting the promise."); 80 | 81 | // Error during download, reject the promise. 82 | promise.Reject(ev.Error); 83 | } 84 | else 85 | { 86 | Console.WriteLine("... Download completed."); 87 | 88 | // Downloaded completed successfully, resolve the promise. 89 | promise.Resolve(ev.Result); 90 | } 91 | }; 92 | 93 | client.DownloadStringAsync(new Uri(url), null); 94 | } 95 | return promise; 96 | } 97 | 98 | // 99 | // LinkItem and LinkFinder from this site: 100 | // 101 | // http://www.dotnetperls.com/scraping-html 102 | // 103 | 104 | public struct LinkItem 105 | { 106 | public string Href; 107 | public string Text; 108 | 109 | public override string ToString() 110 | { 111 | return Href + "\n\t" + Text; 112 | } 113 | } 114 | 115 | static class LinkFinder 116 | { 117 | public static List Find(string file) 118 | { 119 | List list = new List(); 120 | 121 | // 1. 122 | // Find all matches in file. 123 | MatchCollection m1 = Regex.Matches(file, @"(.*?)", 124 | RegexOptions.Singleline); 125 | 126 | // 2. 127 | // Loop over each match. 128 | foreach (Match m in m1) 129 | { 130 | string value = m.Groups[1].Value; 131 | LinkItem i = new LinkItem(); 132 | 133 | // 3. 134 | // Get href attribute. 135 | Match m2 = Regex.Match(value, @"href=\""(.*?)\""", 136 | RegexOptions.Singleline); 137 | if (m2.Success) 138 | { 139 | i.Href = m2.Groups[1].Value; 140 | } 141 | 142 | // 4. 143 | // Remove inner tags from text. 144 | string t = Regex.Replace(value, @"\s*<.*?>\s*", "", 145 | RegexOptions.Singleline); 146 | i.Text = t; 147 | 148 | list.Add(i); 149 | } 150 | return list; 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Examples/Example3/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Example")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Real Serious Games")] 12 | [assembly: AssemblyProduct("Example")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("44f124da-f58f-4c64-94a2-30acaeea3111")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Examples/Example4/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Examples/Example4/Example4.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BDB0135C-B075-47BE-A971-1927BEA6F622} 8 | Exe 9 | Properties 10 | Example 11 | Example 12 | v3.5 13 | 512 14 | 15 | ..\..\ 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {7e1c6a8a-84e7-43c3-ae73-c6e8a9f11af3} 55 | RSG.Promise 56 | 57 | 58 | 59 | 60 | 61 | 62 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /Examples/Example4/Program.cs: -------------------------------------------------------------------------------- 1 | using RSG; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Threading; 9 | 10 | // 11 | // This example downloads search results from google, extracts the links and follows only a single first link, downloads its then prints the result. 12 | // It includes both error handling and a completion handler. 13 | // 14 | namespace Example4 15 | { 16 | class Program 17 | { 18 | // 19 | // URL for a google search on 'promises'. 20 | // 21 | static string searchUrl = "https://www.google.com/#q=promises"; 22 | 23 | static void Main(string[] args) 24 | { 25 | var running = true; 26 | 27 | Download(searchUrl) // Invoke a google search. 28 | .Then(html => // Transforms search results and extract links. 29 | { 30 | return LinkFinder 31 | .Find(html) 32 | .Select(link => link.Href) 33 | .Skip(5) 34 | .First(); // Grab the 6th link. 35 | }) 36 | .Then(firstLink => Download(firstLink)) // Follow the first link and download it. 37 | .Then(html => // Display html from the link that was followed. 38 | { 39 | Console.WriteLine("Async operation completed."); 40 | Console.WriteLine(html.Substring(0, 250) + "..."); 41 | running = false; 42 | }) 43 | .Catch(exception => // Catch any errors that happen during download or transform. 44 | { 45 | Console.WriteLine("Async operation errorred."); 46 | Console.WriteLine(exception); 47 | running = false; 48 | }) 49 | .Done(); 50 | 51 | Console.WriteLine("Waiting"); 52 | 53 | while (running) 54 | { 55 | Thread.Sleep(10); 56 | } 57 | 58 | Console.WriteLine("Exiting"); 59 | } 60 | 61 | /// 62 | /// Download text from a URL. 63 | /// A promise is returned that is resolved when the download has completed. 64 | /// The promise is rejected if an error occurs during download. 65 | /// 66 | static IPromise Download(string url) 67 | { 68 | Console.WriteLine("Downloading " + url + " ..."); 69 | 70 | var promise = new Promise(); 71 | using (var client = new WebClient()) 72 | { 73 | client.DownloadStringCompleted += 74 | (s, ev) => 75 | { 76 | if (ev.Error != null) 77 | { 78 | Console.WriteLine("An error occurred... rejecting the promise."); 79 | 80 | // Error during download, reject the promise. 81 | promise.Reject(ev.Error); 82 | } 83 | else 84 | { 85 | Console.WriteLine("... Download completed."); 86 | 87 | // Downloaded completed successfully, resolve the promise. 88 | promise.Resolve(ev.Result); 89 | } 90 | }; 91 | 92 | client.DownloadStringAsync(new Uri(url), null); 93 | } 94 | return promise; 95 | } 96 | 97 | // 98 | // LinkItem and LinkFinder from this site: 99 | // 100 | // http://www.dotnetperls.com/scraping-html 101 | // 102 | 103 | public struct LinkItem 104 | { 105 | public string Href; 106 | public string Text; 107 | 108 | public override string ToString() 109 | { 110 | return Href + "\n\t" + Text; 111 | } 112 | } 113 | 114 | static class LinkFinder 115 | { 116 | public static List Find(string file) 117 | { 118 | List list = new List(); 119 | 120 | // 1. 121 | // Find all matches in file. 122 | MatchCollection m1 = Regex.Matches(file, @"(.*?)", 123 | RegexOptions.Singleline); 124 | 125 | // 2. 126 | // Loop over each match. 127 | foreach (Match m in m1) 128 | { 129 | string value = m.Groups[1].Value; 130 | LinkItem i = new LinkItem(); 131 | 132 | // 3. 133 | // Get href attribute. 134 | Match m2 = Regex.Match(value, @"href=\""(.*?)\""", 135 | RegexOptions.Singleline); 136 | if (m2.Success) 137 | { 138 | i.Href = m2.Groups[1].Value; 139 | } 140 | 141 | // 4. 142 | // Remove inner tags from text. 143 | string t = Regex.Replace(value, @"\s*<.*?>\s*", "", 144 | RegexOptions.Singleline); 145 | i.Text = t; 146 | 147 | list.Add(i); 148 | } 149 | return list; 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Examples/Example4/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Example")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Real Serious Games")] 12 | [assembly: AssemblyProduct("Example")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("44f124da-f58f-4c64-94a2-30acaeea3111")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Examples/Example5/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Examples/Example5/Example5.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {1060401E-1C17-4741-BF1E-9F05B5427775} 8 | Exe 9 | Properties 10 | Example 11 | Example 12 | v3.5 13 | 512 14 | 15 | ..\..\ 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {7e1c6a8a-84e7-43c3-ae73-c6e8a9f11af3} 55 | RSG.Promise 56 | 57 | 58 | 59 | 60 | 61 | 62 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /Examples/Example5/Program.cs: -------------------------------------------------------------------------------- 1 | using RSG; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Threading; 9 | 10 | // 11 | // This example downloads search results from google, extracts the links, follows all (absolute) links and combines all async operations in a single operation using the All function. 12 | // 13 | namespace Example4 14 | { 15 | class Program 16 | { 17 | // 18 | // URL for a google search on 'promises'. 19 | // 20 | static string searchUrl = "https://www.google.com/#q=promises"; 21 | 22 | static void Main(string[] args) 23 | { 24 | var running = true; 25 | 26 | Download(searchUrl) // Invoke a google search. 27 | .Then(html => // Transforms search results and extract links. 28 | { 29 | return LinkFinder 30 | .Find(html) 31 | .Select(link => link.Href); 32 | }) 33 | // Download all extracted links. 34 | .Then(links => Promise.All( // Combine multiple promises into a single async operation. 35 | links 36 | .Where(link => link.StartsWith("http")) // Filter out relative links. 37 | .Select( // Convert collection of links to a collection of promises that are downloading the links. 38 | link => Download(link) // Download each link. 39 | ) 40 | )) 41 | .Then(htmls => // Display html from the link that was followed. 42 | { 43 | Console.WriteLine("Async operation completed."); 44 | 45 | foreach (var html in htmls) 46 | { 47 | Console.WriteLine("---------------"); 48 | Console.WriteLine(html.Substring(0, 250) + "..."); 49 | } 50 | 51 | Console.WriteLine("---------------"); 52 | Console.WriteLine("Downloaded " + htmls.Count() + " pages"); 53 | 54 | running = false; 55 | }) 56 | .Catch(exception => // Catch any errors that happen during download or transform. 57 | { 58 | Console.WriteLine("Async operation errorred."); 59 | Console.WriteLine(exception); 60 | running = false; 61 | }) 62 | .Done(); 63 | 64 | Console.WriteLine("Waiting"); 65 | 66 | while (running) 67 | { 68 | Thread.Sleep(10); 69 | } 70 | 71 | Console.WriteLine("Exiting"); 72 | } 73 | 74 | /// 75 | /// Download text from a URL. 76 | /// A promise is returned that is resolved when the download has completed. 77 | /// The promise is rejected if an error occurs during download. 78 | /// 79 | static IPromise Download(string url) 80 | { 81 | Console.WriteLine("Downloading " + url + " ..."); 82 | 83 | var promise = new Promise(); 84 | using (var client = new WebClient()) 85 | { 86 | client.DownloadStringCompleted += 87 | (s, ev) => 88 | { 89 | if (ev.Error != null) 90 | { 91 | Console.WriteLine("An error occurred... rejecting the promise."); 92 | 93 | // Error during download, reject the promise. 94 | promise.Reject(ev.Error); 95 | } 96 | else 97 | { 98 | Console.WriteLine("... Download completed."); 99 | 100 | // Downloaded completed successfully, resolve the promise. 101 | promise.Resolve(ev.Result); 102 | } 103 | }; 104 | 105 | client.DownloadStringAsync(new Uri(url), null); 106 | } 107 | return promise; 108 | } 109 | 110 | // 111 | // LinkItem and LinkFinder from this site: 112 | // 113 | // http://www.dotnetperls.com/scraping-html 114 | // 115 | 116 | public struct LinkItem 117 | { 118 | public string Href; 119 | public string Text; 120 | 121 | public override string ToString() 122 | { 123 | return Href + "\n\t" + Text; 124 | } 125 | } 126 | 127 | static class LinkFinder 128 | { 129 | public static List Find(string file) 130 | { 131 | List list = new List(); 132 | 133 | // 1. 134 | // Find all matches in file. 135 | MatchCollection m1 = Regex.Matches(file, @"(.*?)", 136 | RegexOptions.Singleline); 137 | 138 | // 2. 139 | // Loop over each match. 140 | foreach (Match m in m1) 141 | { 142 | string value = m.Groups[1].Value; 143 | LinkItem i = new LinkItem(); 144 | 145 | // 3. 146 | // Get href attribute. 147 | Match m2 = Regex.Match(value, @"href=\""(.*?)\""", 148 | RegexOptions.Singleline); 149 | if (m2.Success) 150 | { 151 | i.Href = m2.Groups[1].Value; 152 | } 153 | 154 | // 4. 155 | // Remove inner tags from text. 156 | string t = Regex.Replace(value, @"\s*<.*?>\s*", "", 157 | RegexOptions.Singleline); 158 | i.Text = t; 159 | 160 | list.Add(i); 161 | } 162 | return list; 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Examples/Example5/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Example")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Real Serious Games")] 12 | [assembly: AssemblyProduct("Example")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("44f124da-f58f-4c64-94a2-30acaeea3111")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Real Serious Games 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C-Sharp-Promise [![Build Status](https://travis-ci.org/Real-Serious-Games/C-Sharp-Promise.svg)](https://travis-ci.org/Real-Serious-Games/C-Sharp-Promise) [![NuGet](https://img.shields.io/nuget/v/RSG.Promise.svg)](https://www.nuget.org/packages/RSG.Promise/) # 2 | 3 | 4 | Promises/A+ logo 6 | 7 | 8 | Promises library for C# for management of asynchronous operations. 9 | 10 | Inspired by JavaScript promises, but slightly different. 11 | 12 | Used by [Real Serious Games](https://github.com/Real-Serious-Games/C-Sharp-Promise) for building serious games and simulations on Unity3d. 13 | 14 | If you are interested in using promises for game development and Unity please see [this article](http://www.what-could-possibly-go-wrong.com/promises-for-game-development/). 15 | 16 | ## Recent Updates 17 | 18 | - v3.0 (15 Feburary 2018) 19 | - *Finally* has been modified to work in a way consistent to [Promise.prototype.finally()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally) in JavaScript. 20 | - Added support for reporting progress in a promise. 21 | - Signed assembly with a strong name. 22 | - Errors throw custom exception types rather than generic ones. 23 | - Modified some overloads of *Then* that didn't make sense. 24 | - v2.0 (4 December 2017) 25 | - *Then* functions chained after a *Catch* are now run after the exception is handled rather than being terminated 26 | - *Catch* can return a value which will be passed into the next *Then* 27 | - The *onResolved* callback of *Then* can now also return a value which is passed to the next promise in the same way 28 | - Added *elapsedUpdates* property to the TimeData struct used by PromiseTimer 29 | - v1.3 (28 October 2017) 30 | - Added Cancel method to PromiseTimer 31 | - Implemented an overload of Promise.All that works on Tuples of multiple types 32 | - Implemented Finally method 33 | - Removed dependency on RSG.Toolkit 34 | - v1.2 (8 March 2015) 35 | - *Transform* function has been renamed to *Then* (another overload of *Then*). 36 | 37 | ## Projects using this library 38 | - **[RestClient for Unity 🤘](https://github.com/proyecto26/RestClient)** 39 | 40 | ## Contents 41 | 42 | 43 | 44 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 45 | 46 | - [Understanding Promises](#understanding-promises) 47 | - [Promises/A+ Spec](#promisesa-spec) 48 | - [Getting the DLL](#getting-the-dll) 49 | - [Getting the Code](#getting-the-code) 50 | - [Creating a Promise for an Async Operation](#creating-a-promise-for-an-async-operation) 51 | - [Creating a Promise, Alternate Method](#creating-a-promise-alternate-method) 52 | - [Waiting for an Async Operation to Complete](#waiting-for-an-async-operation-to-complete) 53 | - [Chaining Async Operations](#chaining-async-operations) 54 | - [Transforming the Results](#transforming-the-results) 55 | - [Error Handling](#error-handling) 56 | - [Unhandled Errors](#unhandled-errors) 57 | - [Progress reporting](#progress-reporting) 58 | - [Promises that are already Resolved/Rejected](#promises-that-are-already-resolvedrejected) 59 | - [Interfaces](#interfaces) 60 | - [Combining Multiple Async Operations](#combining-multiple-async-operations) 61 | - [Chaining Multiple Async Operations](#chaining-multiple-async-operations) 62 | - [Racing Asynchronous Operations](#racing-asynchronous-operations) 63 | - [Chaining Synchronous Actions that have no Result](#chaining-synchronous-actions-that-have-no-result) 64 | - [Promises that have no Results (a non-value promise)](#promises-that-have-no-results-a-non-value-promise) 65 | - [Convert a value promise to a non-value promise](#convert-a-value-promise-to-a-non-value-promise) 66 | - [Running a Sequence of Operations](#running-a-sequence-of-operations) 67 | - [Combining Parallel and Sequential Operations](#combining-parallel-and-sequential-operations) 68 | - [Weighted averaging of progress on multiple promises](#weighted-averaging-of-progress-on-multiple-promises) 69 | - [PromiseTimer class](#promisetimer-class) 70 | - [PromiseTimer.WaitFor](#promisetimerwaitfor) 71 | - [PromiseTimer.WaitUntil](#promisetimerwaituntil) 72 | - [PromiseTimer.WaitWhile](#promisetimerwaitwhile) 73 | - [TimeData struct](#timedata-struct) 74 | - [Examples](#examples) 75 | 76 | 77 | 78 | ## Understanding Promises 79 | 80 | To learn about promises: 81 | 82 | - [Promises on Wikpedia](http://en.wikipedia.org/wiki/Futures_and_promises) 83 | - [Good overview](https://www.promisejs.org/) 84 | - [Mozilla](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) 85 | 86 | ## Promises/A+ Spec 87 | 88 | This promise library conforms to the [Promises/A+ Spec](https://promisesaplus.com/) (at least, as far as is possible with C#): 89 | 90 | ## Getting the DLL 91 | 92 | The DLL can be installed via nuget. Use the Package Manager UI or console in Visual Studio or use nuget from the command line. 93 | 94 | See here for instructions on installing a package via nuget: http://docs.nuget.org/docs/start-here/using-the-package-manager-console 95 | 96 | The package to search for is *RSG.Promise*. 97 | 98 | ## Getting the Code 99 | 100 | You can get the code by cloning the github repository. You can do this in a UI like SourceTree or you can do it from the command line as follows: 101 | 102 | git clone https://github.com/Real-Serious-Games/C-Sharp-Promise.git 103 | 104 | Alternately, to contribute please fork the project in github. 105 | 106 | ## Creating a Promise for an Async Operation ## 107 | 108 | Reference the DLL and import the namespace: 109 | ```cs 110 | using RSG; 111 | ``` 112 | Create a promise before you start the async operation: 113 | ```cs 114 | var promise = new Promise(); 115 | ``` 116 | The type of the promise should reflect the result of the async op. 117 | 118 | Then initiate your async operation and return the promise to the caller. 119 | 120 | Upon completion of the async op the promise is resolved: 121 | ```cs 122 | promise.Resolve(myValue); 123 | ``` 124 | The promise is rejected on error/exception: 125 | ```cs 126 | promise.Reject(myException); 127 | ``` 128 | To see it in context, here is an example function that downloads text from a URL. The promise is resolved when the download completes. If there is an error during download, say *unresolved domain name*, then the promise is rejected: 129 | 130 | ```cs 131 | public IPromise Download(string url) 132 | { 133 | var promise = new Promise(); // Create promise. 134 | using (var client = new WebClient()) 135 | { 136 | client.DownloadStringCompleted += // Monitor event for download completed. 137 | (s, ev) => 138 | { 139 | if (ev.Error != null) 140 | { 141 | promise.Reject(ev.Error); // Error during download, reject the promise. 142 | } 143 | else 144 | { 145 | promise.Resolve(ev.Result); // Downloaded completed successfully, resolve the promise. 146 | } 147 | }; 148 | 149 | client.DownloadStringAsync(new Uri(url), null); // Initiate async op. 150 | } 151 | 152 | return promise; // Return the promise so the caller can await resolution (or error). 153 | } 154 | ``` 155 | 156 | ## Creating a Promise, Alternate Method 157 | 158 | There is another way to create a promise that replicates the JavaScript convention of passing a *resolver* function into the constructor. The resolver function is passed functions that resolve or reject the promise. This allows you to express the previous example like this: 159 | 160 | ```cs 161 | var promise = new Promise((resolve, reject) => 162 | { 163 | using (var client = new WebClient()) 164 | { 165 | client.DownloadStringCompleted += // Monitor event for download completed. 166 | (s, ev) => 167 | { 168 | if (ev.Error != null) 169 | { 170 | reject(ev.Error); // Error during download, reject the promise. 171 | } 172 | else 173 | { 174 | resolve(ev.Result); // Downloaded completed successfully, resolve the promise. 175 | } 176 | }; 177 | 178 | client.DownloadStringAsync(new Uri(url), null); // Initiate async op. 179 | } 180 | }); 181 | ``` 182 | 183 | ## Waiting for an Async Operation to Complete ## 184 | 185 | The simplest usage is to register a completion handler to be invoked on completion of the async op: 186 | ```cs 187 | Download("http://www.google.com") 188 | .Then(html => 189 | Console.WriteLine(html) 190 | ); 191 | ``` 192 | 193 | This snippet downloads the front page from Google and prints it to the console. 194 | 195 | For all but the most trivial applications you will also want to register an error hander: 196 | ```cs 197 | Download("http://www.google.com") 198 | .Then(html => 199 | Console.WriteLine(html) 200 | ) 201 | .Catch(exception => 202 | Console.WriteLine("An exception occured while downloading!") 203 | ); 204 | ``` 205 | 206 | The chain of processing for a promise ends as soon as an error/exception occurs. In this case when an error occurs the *Catch* handler would be called, but not the *Done* handler. If there is no error, then only *Done* is called. 207 | 208 | ## Chaining Async Operations ## 209 | 210 | Multiple async operations can be chained one after the other using *Then*: 211 | ```cs 212 | Download("http://www.google.com") 213 | .Then(html => 214 | return Download(ExtractFirstLink(html)) // Extract the first link and download it. 215 | ) 216 | .Then(firstLinkHtml => 217 | Console.WriteLine(firstLinkHtml) 218 | ) 219 | .Catch(exception => 220 | Console.WriteLine("An exception occured while downloading!") 221 | ); 222 | ``` 223 | 224 | Here we are chaining another download onto the end of the first download. The first link in the html is extracted and we then download that. *Then* expects the return value to be another promise. The chained promise can have a different *result type*. 225 | 226 | ## Transforming the Results ## 227 | 228 | Sometimes you will want to simply transform or modify the resulting value without chaining another async operation. 229 | ```cs 230 | Download("http://www.google.com") 231 | .Then(html => 232 | return ExtractAllLinks(html)) // Extract all links and return an array of strings. 233 | ) 234 | .Then(links => // The input here is an array of strings. 235 | foreach (var link in links) 236 | { 237 | Console.WriteLine(link); 238 | } 239 | ); 240 | ``` 241 | 242 | As is demonstrated the type of the value can also be changed during transformation. In the previous snippet a `Promise` is transformed to a `Promise`. 243 | 244 | ## Error Handling 245 | 246 | An error raised in a callback aborts the function and all subsequent callbacks in the chain: 247 | ```cs 248 | promise.Then(v => Something()) // <--- An error here aborts all subsequent callbacks... 249 | .Then(v => SomethingElse()) 250 | .Then(v => AnotherThing()) 251 | .Catch(e => HandleError(e)) // <--- Until the error handler is invoked here. 252 | ``` 253 | 254 | ## Unhandled Errors 255 | 256 | When `Catch` is omitted exceptions go silently unhandled. This is an acknowledged issue with the Promises pattern. 257 | 258 | We handle this in a similar way to the JavaScript [Q](http://documentup.com/kriskowal/q) library. The `Done` method is used to terminate a chain, it registers a default catch handler that propagates unhandled exceptions to a default error handling mechanism that can be hooked into by the user. 259 | 260 | Terminating a Promise chain using `Done`: 261 | ```cs 262 | promise.Then(v => Something()) 263 | .Then(v => SomethingElse()) 264 | .Then(v => AnotherThing()) 265 | .Done(); // <--- Terminate the pipeline and propagate unhandled exceptions. 266 | ``` 267 | 268 | To use the `Done` you must apply the following rule: When you get to the end of a chain of promises, you should either return the last promise or end the chain by calling `Done`. 269 | 270 | To hook into the unhandled exception stream: 271 | ```cs 272 | Promise.UnhandledException += Promise_UnhandledException; 273 | ``` 274 | 275 | Then forward the exceptions to your own logging system: 276 | ```cs 277 | private void Promise_UnhandledException(object sender, ExceptionEventArgs e) 278 | { 279 | Log.Error(e.Exception, "An unhandled promises exception occured!"); 280 | } 281 | ``` 282 | 283 | ## Progress reporting 284 | 285 | Promises can additionally report their progress towards completion, allowing the implementor to give the user feedback on the asynchronous operation. The general convention is to report progress as a value from 0 to 1. 286 | 287 | For this, you can either call `Progress` in the promise definition chain or add a third parameter to the `Then` method. 288 | 289 | Listening for progress reporting from a promise using `Progress`: 290 | ```cs 291 | var promise = new Promise(); 292 | promise.Progress((progress) => Log.Info("Current progress is " + (100f * progress) + "%")); 293 | ``` 294 | 295 | Listening for progress on a `Then` call: 296 | ```cs 297 | var promiseA = new Promise(); 298 | var promiseB = new Promise(); 299 | promise 300 | .Then(() => promiseB, null, (progress) => Log.Info("promiseA made progress: " + progress)) 301 | .Progress(progress => Log.Info("promiseB made progress: " + progress)); 302 | ``` 303 | 304 | In order to report progress for a promise, you need to call the `ReportProgress` method: 305 | ```cs 306 | var promise = new Promise(); 307 | promise.ReportProgress(0.5f); // Report a 50% completion 308 | ``` 309 | 310 | ## Promises that are already Resolved/Rejected 311 | 312 | For convenience or testing you will at some point need to create a promise that *starts out* in the resolved or rejected state. This is easy to achieve using *Resolved* and *Rejected* functions: 313 | ```cs 314 | var resolvedPromise = Promise.Resolved("some result"); 315 | 316 | var rejectedPromise = Promise.Rejected(someException); 317 | ``` 318 | 319 | ## Interfaces ## 320 | 321 | The class *Promise* implements the following interfaces: 322 | 323 | - `IPromise` Interface to await promise resolution. 324 | - `IPendingPromise` Interface that can resolve or reject the promise. 325 | 326 | ## Combining Multiple Async Operations ## 327 | 328 | The *All* function combines multiple async operations to run in parallel. It converts a collection of promises or a variable length parameter list of promises into a single promise that yields a collection. 329 | 330 | Say that each promise yields a value of type *T*, the resulting promise then yields a collection with values of type *T*. 331 | 332 | Here is an example that extracts links from multiple pages and merges the results: 333 | ```cs 334 | var urls = new List(); 335 | urls.Add("www.google.com"); 336 | urls.Add("www.yahoo.com"); 337 | 338 | Promise 339 | .All(urls.Select(url => Download(url))) // Download each URL. 340 | .Then(pages => // Receives collection of downloaded pages. 341 | pages.SelectMany( 342 | page => ExtractAllLinks(page) // Extract links from all pages then flatten to single collection of links. 343 | ) 344 | ) 345 | .Done(links => // Receives the flattened collection of links from all pages at once. 346 | { 347 | foreach (var link in links) 348 | { 349 | Console.WriteLine(link); 350 | } 351 | }); 352 | ``` 353 | 354 | When listening for progress events in an All operation, the progress that you will receive will be the average of all the progress values reported by all the given promises. 355 | 356 | ## Chaining Multiple Async Operations 357 | 358 | The *ThenAll* function is a convenient way of chaining multiple promise onto an existing promise: 359 | ```cs 360 | promise 361 | .Then(result => SomeAsyncOperation(result)) // Chain a single async operation 362 | .ThenAll(result => // Chain multiple async operations. 363 | new IPromise[] // Return an enumerable of promises. 364 | { 365 | SomeAsyncOperation1(result), 366 | SomeAsyncOperation2(result), 367 | SomeAsyncOperation3(result) 368 | } 369 | ) 370 | .Done(collection => ...); // Final promise resolves 371 | // with a collection of values 372 | // when all operations have completed. 373 | ``` 374 | 375 | ## Racing Asynchronous Operations 376 | 377 | The *Race* and *ThenRace* functions are similar to the *All* and *ThenAll* functions, but it is the first async operation that completes that wins the race and it's value resolves the promise. 378 | ```cs 379 | promise 380 | .Then(result => SomeAsyncOperation(result)) // Chain an async operation. 381 | .ThenRace(result => // Race multiple async operations. 382 | new IPromise[] // Return an enumerable of promises. 383 | { 384 | SomeAsyncOperation1(result), 385 | SomeAsyncOperation2(result), 386 | SomeAsyncOperation3(result) 387 | } 388 | ) 389 | .Done(result => ...); // The result has come from whichever of 390 | // the async operations completed first. 391 | ``` 392 | 393 | When listening for progress events in a race operation, the progress that you will receive will be the maximum of those reported by all the given promises. 394 | 395 | ## Chaining Synchronous Actions that have no Result 396 | 397 | The *Then* function can be used to chain synchronous operations that yield no result. 398 | ```cs 399 | var promise = ... 400 | promise 401 | .Then(result => SomeAsyncOperation(result)) // Chain an async operation. 402 | .Then(result => Console.WriteLine(result)) // Chain a sync operation that yields no result. 403 | .Done(() => ...); // No result is passed because the previous operation returned nothing. 404 | ``` 405 | 406 | ## Promises that have no Results (a non-value promise) 407 | 408 | What about a promise that has no result? This represents an asynchronous operation that promises only to complete, it doesn't promise to yield any value as a result. I call this a non-value promise, as opposed to a value promise, which is a promise that does yield a value. This might seem like a curiousity but it is actually very useful for sequencing visual effects. 409 | 410 | `Promise` is very similar to `Promise` and implements the similar interfaces: `IPromise` and `IPendingPromise`. 411 | 412 | `Promise` functions that affect the resulting value have no relevance for the non-value promise and have been removed. 413 | 414 | As an example consider the chaining of animation and sound effects as we often need to do in *game development*: 415 | ```cs 416 | RunAnimation("Foo") // RunAnimation returns a promise that 417 | .Then(() => RunAnimation("Bar")) // is resolved when the animation is complete. 418 | .Then(() => PlaySound("AnimComplete")); 419 | ``` 420 | 421 | ## Convert a value promise to a non-value promise 422 | 423 | From time to time you might want to convert a value promise to a non-value promise or vice versa. Both `Promise` and `Promise` have overloads of `Then` and `ThenAll` that do this conversion. You just need to return the appropriate type of promise (for `Then`) or enumerable of promises (for `ThenAll`). 424 | 425 | As an example consider a recursive link extractor and file downloader function: 426 | ```cs 427 | public IPromise DownloadAll(string url) 428 | { 429 | return DownloadURL(url) // Yields a value, the HTML text downloaded. 430 | .Then(html => ExtractLinks(html)) // Convert HTML into an enumerable of links. 431 | .ThenAll(links => // Process each link. 432 | { 433 | // Determine links that should be followed, then follow them. 434 | var linksToFollow = links.Where(link => IsLinkToFollow(link)); 435 | var linksFollowing = linksToFollow.Select(link => DownloadAll(link)); 436 | 437 | // Determine links that are files to be downloaded, then download them. 438 | var linksToDownload = links.Where(link => IsLinkToDownload(link)); 439 | var linksDownloading = linksToDownload.Select(link => DownloadFile(link)); 440 | 441 | // Return an enumerable of promises. 442 | // This combines the recursive link following and any files we want to download. 443 | // Because we are returning an enumerable of non-value promises, the resulting 444 | // chained promises is also non-value. 445 | return linksToFollow.Concat(linksDownloading); 446 | }); 447 | } 448 | ``` 449 | 450 | Usage: 451 | ```cs 452 | DownloadAll("www.somewhere.com") 453 | .Done(() => 454 | Console.WriteLine("Recursive download completed."); 455 | ); 456 | ``` 457 | 458 | ## Running a Sequence of Operations 459 | 460 | The `Sequence` and `ThenSequence` functions build a single promise that wraps multiple sequential operations that will be invoked one after the other. 461 | 462 | Multiple promise-yielding functions are provided as input, these are chained one after the other and wrapped in a single promise that is resolved once the sequence has completed. 463 | ```cs 464 | var sequence = Promise.Sequence( 465 | () => RunAnimation("Foo"), 466 | () => RunAnimation("Bar"), 467 | () => PlaySound("AnimComplete") 468 | ); 469 | ``` 470 | 471 | The inputs can also be passed as a collection: 472 | ```cs 473 | var operations = ... 474 | var sequence = Promise.Sequence(operations); 475 | ``` 476 | 477 | This might be used, for example, to play a variable length collection of animations based on data: 478 | ```cs 479 | var animationNames = ... variable length array of animation names loaded from data... 480 | var animations = animationNames.Select(animName => (Func)(() => RunAnimation(animName))); 481 | var sequence = Promise.Sequence(animations); 482 | sequence 483 | .Done(() => 484 | { 485 | // All animations have completed in sequence. 486 | }); 487 | ``` 488 | 489 | Unfortunately we find that we have reached the limits of what is possible with C# type inference, hence the use of the ugly cast `(Func)`. 490 | 491 | The cast can easily be removed by converting the inner anonymous function to an actual function which I'll call `PrepAnimation`: 492 | ```cs 493 | private Func PrepAnimation(string animName) 494 | { 495 | return () => RunAnimation(animName); 496 | } 497 | 498 | var animations = animationNames.Select(animName => PrepAnimation(animName)); 499 | var sequence = Promise.Sequence(animations); 500 | sequence 501 | .Done(() => 502 | { 503 | // All animations have completed in sequence. 504 | }); 505 | ``` 506 | 507 | Holy cow... we've just careened into [functional programming](http://en.wikipedia.org/wiki/Functional_programming) territory, herein lies very powerful and expressive programming techniques. 508 | 509 | ## Combining Parallel and Sequential Operations 510 | 511 | We can easily combine sequential and parallel operations to build very expressive logic. 512 | ```cs 513 | Promise.Sequence( // Play operations 1 and 2 sequently. 514 | () => Promise.All( // Operation 1: Play animation and sound at same time. 515 | RunAnimation("Foo"), 516 | PlaySound("Bar") 517 | ), 518 | () => Promise.All( 519 | RunAnimation("One"), // Operation 2: Play animation and sound at same time. 520 | PlaySound("Two") 521 | ) 522 | ); 523 | ``` 524 | 525 | I'm starting to feel like we are defining behavior trees. 526 | 527 | ## Weighted averaging of progress on multiple promises 528 | 529 | If you have a promise that comprises a sequence of other promises, you might want to report the total progress for these, and even give more weight to the progress of some promise over another. In this example, we are first downloading an asset from some URL and then we are loading the downloaded asset into memory. We consider that the time it takes to download the asset will be an 80% of the total time, while the time to load it into memory is a 20%: 530 | ```cs 531 | var promise = new Promise(); 532 | 533 | Download(url) 534 | .Progress((downloadProgress) => promise.ReportProgress(0.8f * downloadProgress)) 535 | .Then((asset) => LoadAssetIntoMemory(asset)) 536 | .Progress((loadProgress) => promise.ReportProgress(0.8f + 0.2f * loadProgress)) 537 | .Then(() => promise.Resolve()) 538 | .Catch((ex) => promise.Reject(ex)); 539 | 540 | return promise; 541 | ``` 542 | 543 | ## PromiseTimer class 544 | 545 | The promise timer is not part of the Promises/A+ standard but is a utility that makes it possible to create promises that check if a condition is met each time the promise timer is updated. A common usage of this is in games where the promise timer is updated each frame. 546 | 547 | To use it, create an instance of the promise timer and call its `Update` method in your main loop: 548 | ```cs 549 | class Example 550 | { 551 | private IPromiseTimer promiseTimer; 552 | 553 | Example() 554 | { 555 | promiseTimer = new PromiseTimer(); 556 | } 557 | 558 | // Run once for every frame - equivilant to Update() in Unity 559 | void MainLoop() 560 | { 561 | // deltaTime is equal to the time since the last MainLoop 562 | promiseTimer.Update(Time.deltaTime); 563 | 564 | // Process your other logic here 565 | } 566 | } 567 | ``` 568 | 569 | Note that usually it is best to call `PromiseTimer.Update` *before* your other logic, otherwise you may have unintended behaviour such as promises that are supposed to take a very short time resolving in the same update loop as they were created in. 570 | 571 | ### PromiseTimer.WaitFor 572 | 573 | This method creates a promise that resolves after the specified amount of time in seconds has passed. Time is calculated as the sum of the delta values passed into `PromiseTimer.Update` 574 | ```cs 575 | IPromise LogAfterFourSeconds() 576 | { 577 | return promiseTimer.WaitFor(4f) 578 | .Then(() => Console.Log("4 seconds have passed!")); 579 | } 580 | ``` 581 | 582 | ### PromiseTimer.WaitUntil 583 | 584 | WaitUntil takes a predicate to check each update and resolves once the predicate returns true. This predicate function is passed a `TimeData` object, which just contains the most recent frame's `deltaTime` and `elapsedTime` which is the total amount of time since the promise was created. 585 | ```cs 586 | IPromise FadeOut(float duration) 587 | { 588 | return promiseTimer.WaitUntil(timeData => 589 | { 590 | // Here we are using the amount of elapsed time to calculate what the current 591 | // fade value should be (between 0 and 1). 592 | // Since we're fading our we should be going from 0 (not faded) to 1 (full) 593 | var fadeAmount = Mathf.Clamp01(timeData.elapsedTime / duration); 594 | SetFadeValue(fadeAmount); 595 | 596 | // Resolve the promsie once we've finished. 597 | return fadeAmount >= 1f; 598 | }); 599 | } 600 | ``` 601 | 602 | ### PromiseTimer.WaitWhile 603 | 604 | WaitWhile is exactly the same as WaitUntil except that it resolves when its predicate function returns false. Think of WaitUntil as running *until* its predicate returns true, and WaitWhile as running *while* its predicate returns true, stopping when it is false. 605 | 606 | ### TimeData struct 607 | 608 | TimeData is passed to you as a paramter when using either PromiseTimer.WaitUntil or PromiseTimer.WaitWhile. It contains the following public fields: 609 | 610 | - elapsedTime 611 | - The amount of time that has elapsed since the pending promise started running 612 | - deltaTime 613 | - The amount of time since the last time the pending promise was updated. 614 | - elapsedUpdates 615 | - The amount of times that PromiseTimer.Update() has been called since the pending promise started running 616 | 617 | ## Examples 618 | 619 | 620 | - Example1 621 | - Example of downloading text from a URL using a promise. 622 | - Example2 623 | - Example of a promise that is rejected because of an error during 624 | - the async operation. 625 | - Example3 626 | - This example downloads search results from google then transforms the result to extract links. 627 | - Includes both error handling and a completion handler. 628 | - Example4 629 | - This example downloads search results from google, extracts the links and follows only a single first link, downloads its then prints the result. 630 | - Includes both error handling and a completion handler. 631 | - Example5 632 | - This example downloads search results from google, extracts the links, follows all (absolute) links and combines all async operations in a single operation using the `All` function. 633 | -------------------------------------------------------------------------------- /Tests/A+ Spec/2.1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RSG.Exceptions; 3 | using Xunit; 4 | 5 | namespace RSG.Tests.A__Spec 6 | { 7 | public class _2_1 8 | { 9 | // 2.1.1.1. 10 | [Fact] 11 | public void When_pending_a_promise_may_transition_to_either_the_fulfilled_or_rejected_state() 12 | { 13 | var pendingPromise1 = new Promise(); 14 | Assert.Equal(PromiseState.Pending, pendingPromise1.CurState); 15 | pendingPromise1.Resolve(new object()); 16 | Assert.Equal(PromiseState.Resolved, pendingPromise1.CurState); 17 | 18 | var pendingPromise2 = new Promise(); 19 | Assert.Equal(PromiseState.Pending, pendingPromise2.CurState); 20 | pendingPromise2.Reject(new Exception()); 21 | Assert.Equal(PromiseState.Rejected, pendingPromise2.CurState); 22 | } 23 | 24 | // 2.1.2 25 | public class When_fulfilled_a_promise_ 26 | { 27 | // 2.1.2.1 28 | [Fact] 29 | public void _must_not_transition_to_any_other_state() 30 | { 31 | var fulfilledPromise = new Promise(); 32 | fulfilledPromise.Resolve(new object()); 33 | 34 | Assert.Throws(() => fulfilledPromise.Reject(new Exception())); 35 | 36 | Assert.Equal(PromiseState.Resolved, fulfilledPromise.CurState); 37 | } 38 | 39 | // 2.1.2.2 40 | [Fact] 41 | public void _must_have_a_value_which_must_not_change() 42 | { 43 | var promisedValue = new object(); 44 | var fulfilledPromise = new Promise(); 45 | var handled = 0; 46 | 47 | fulfilledPromise.Then(v => 48 | { 49 | Assert.Equal(promisedValue, v); 50 | ++handled; 51 | }); 52 | 53 | fulfilledPromise.Resolve(promisedValue); 54 | 55 | Assert.Throws(() => fulfilledPromise.Resolve(new object())); 56 | 57 | Assert.Equal(1, handled); 58 | } 59 | } 60 | 61 | // 2.1.3 62 | public class When_rejected_a_promise_ 63 | { 64 | // 2.1.3.1 65 | [Fact] 66 | public void _must_not_transition_to_any_other_state() 67 | { 68 | var rejectedPromise = new Promise(); 69 | rejectedPromise.Reject(new Exception()); 70 | 71 | Assert.Throws(() => rejectedPromise.Resolve(new object())); 72 | 73 | Assert.Equal(PromiseState.Rejected, rejectedPromise.CurState); 74 | } 75 | 76 | // 2.1.3.21 77 | [Fact] 78 | public void _must_have_a_reason_which_must_not_change() 79 | { 80 | var rejectedPromise = new Promise(); 81 | var reason = new Exception(); 82 | var handled = 0; 83 | 84 | rejectedPromise.Catch(e => 85 | { 86 | Assert.Equal(reason, e); 87 | ++handled; 88 | }); 89 | 90 | rejectedPromise.Reject(reason); 91 | 92 | Assert.Throws(() => rejectedPromise.Reject(new Exception())); 93 | 94 | Assert.Equal(1, handled); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Tests/A+ Spec/2.2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace RSG.Tests.A__Spec 5 | { 6 | public class _2_2 7 | { 8 | // 2.2.1 9 | public class _both_onFulfilled_and_onRejected_are_optional_arguments_ 10 | { 11 | // 2.2.1.1 12 | [Fact] 13 | public void _if_onFulfilled_is_not_a_function_it_must_be_ignored() 14 | { 15 | var promise = new Promise(); 16 | 17 | var resultPromise = promise 18 | .Then( 19 | (Func)null, 20 | ex => { } 21 | ); 22 | 23 | var resolves = 0; 24 | var errors = 0; 25 | resultPromise.Then(() => ++resolves); 26 | resultPromise.Catch(ex => ++errors); 27 | 28 | promise.Resolve(new object()); 29 | 30 | Assert.Equal(1, resolves); 31 | Assert.Equal(0, errors); 32 | } 33 | 34 | // 2.2.1.2 35 | [Fact] 36 | public void _if_onRejected_is_not_a_function_it_must_be_ignored_1() 37 | { 38 | var promise = new Promise(); 39 | 40 | var resultPromise = promise 41 | .Then( 42 | v => Promise.Resolved(), 43 | null 44 | ); 45 | 46 | var resolved = 0; 47 | var errors = 0; 48 | var e = new Exception(); 49 | resultPromise.Then(() => ++resolved); 50 | resultPromise.Catch(ex => 51 | { 52 | Assert.Equal(e, ex); 53 | ++errors; 54 | }); 55 | 56 | promise.Reject(e); 57 | 58 | Assert.Equal(0, resolved); 59 | Assert.Equal(1, errors); 60 | } 61 | 62 | [Fact] 63 | public void _if_onRejected_is_not_a_function_it_must_be_ignored_2() 64 | { 65 | var promise = new Promise(); 66 | 67 | var resultPromise = promise 68 | .Then( 69 | v => Promise.Resolved(new object()), 70 | null 71 | ); 72 | 73 | var resolved = 0; 74 | var errors = 0; 75 | var e = new Exception(); 76 | resultPromise.Then(v => ++resolved); 77 | resultPromise.Catch(ex => 78 | { 79 | Assert.Equal(e, ex); 80 | ++errors; 81 | }); 82 | 83 | promise.Reject(e); 84 | 85 | Assert.Equal(0, resolved); 86 | Assert.Equal(1, errors); 87 | } 88 | } 89 | 90 | // 2.2.2 91 | public class _if_onFulfilled_is_a_function_ 92 | { 93 | // 2.2.2.1 94 | [Fact] 95 | public void _it_must_be_called_after_promise_is_fulfilled_with_promises_value_as_its_first_argument() 96 | { 97 | var promise = new Promise(); 98 | 99 | var promisedValue = new object(); 100 | var resolved = false; 101 | 102 | promise.Then( 103 | v => 104 | { 105 | Assert.Equal(promisedValue, v); 106 | resolved = true; 107 | }, 108 | null 109 | ); 110 | 111 | promise.Resolve(promisedValue); 112 | 113 | Assert.True(resolved); 114 | } 115 | 116 | // 2.2.2.2 117 | [Fact] 118 | public void _it_must_not_be_called_before_promise_is_fulfilled() 119 | { 120 | var promise = new Promise(); 121 | var resolved = false; 122 | 123 | promise.Then( 124 | v => resolved = true, 125 | null 126 | ); 127 | 128 | Assert.False(resolved); 129 | } 130 | 131 | // 2.2.2.3 132 | [Fact] 133 | public void _it_must_not_be_called_more_than_once() 134 | { 135 | var promise = new Promise(); 136 | var promisedValue = new object(); 137 | var resolved = 0; 138 | 139 | promise.Then( 140 | v => ++resolved, 141 | null 142 | ); 143 | 144 | promise.Resolve(promisedValue); 145 | 146 | Assert.Equal(1, resolved); 147 | } 148 | } 149 | 150 | // 2.2.3 151 | public class _If_onRejected_is_a_function_ 152 | { 153 | // 2.2.3.1 154 | [Fact] 155 | public void _it_must_be_called_after_promise_is_rejected_with_promises_reason_as_its_first_argument() 156 | { 157 | var promise = new Promise(); 158 | var rejectedReason = new Exception(); 159 | var errored = false; 160 | 161 | promise.Then( 162 | v => {}, 163 | ex => 164 | { 165 | Assert.Equal(rejectedReason, ex); 166 | errored = true; 167 | } 168 | ); 169 | 170 | promise.Reject(rejectedReason); 171 | 172 | Assert.True(errored); 173 | } 174 | 175 | // 2.2.3.2 176 | [Fact] 177 | public void _it_must_not_be_called_before_promise_is_rejected() 178 | { 179 | var promise = new Promise(); 180 | var errored = false; 181 | 182 | promise.Then( 183 | v => {}, 184 | ex => errored = true 185 | ); 186 | 187 | Assert.False(errored); 188 | } 189 | 190 | // 2.2.3.3 191 | [Fact] 192 | public void _it_must_not_be_called_more_than_once() 193 | { 194 | var promise = new Promise(); 195 | var rejectedReason = new Exception(); 196 | var errored = 0; 197 | 198 | promise.Then( 199 | v => {}, 200 | ex => ++errored 201 | ); 202 | 203 | promise.Reject(rejectedReason); 204 | 205 | Assert.Equal(1, errored); 206 | } 207 | } 208 | 209 | // 2.2.4 210 | // Not really appropriate in C#. 211 | 212 | // 2.2.5 213 | // Not really appropriate in C#. 214 | 215 | // 2.2.6 216 | public class then_may_be_called_multiple_times_on_the_same_promise_ 217 | { 218 | // 2.2.6.1 219 | [Fact] 220 | public void _when_promise_is_fulfilled_all_respective_onFulfilled_callbacks_must_execute_in_the_order_of_their_originating_calls_to_then_1() 221 | { 222 | var promise = new Promise(); 223 | 224 | var order = 0; 225 | 226 | promise.Then(_ => 227 | { 228 | Assert.Equal(1, ++order); 229 | }); 230 | promise.Then(_ => 231 | { 232 | Assert.Equal(2, ++order); 233 | }); 234 | promise.Then(_ => 235 | { 236 | Assert.Equal(3, ++order); 237 | }); 238 | 239 | promise.Resolve(new object()); 240 | 241 | Assert.Equal(3, order); 242 | } 243 | 244 | [Fact] 245 | public void _when_promise_is_fulfilled_all_respective_onFulfilled_callbacks_must_execute_in_the_order_of_their_originating_calls_to_then_2() 246 | { 247 | var promise = new Promise(); 248 | 249 | var order = 0; 250 | 251 | promise.Then(_ => 252 | { 253 | Assert.Equal(1, ++order); 254 | 255 | return Promise.Resolved(new object()); 256 | }); 257 | 258 | promise.Then(_ => 259 | { 260 | Assert.Equal(2, ++order); 261 | 262 | return Promise.Resolved(new object()); 263 | }); 264 | 265 | promise.Then(_ => 266 | { 267 | Assert.Equal(3, ++order); 268 | 269 | return Promise.Resolved(new object()); 270 | }); 271 | 272 | promise.Resolve(new object()); 273 | 274 | Assert.Equal(3, order); 275 | } 276 | 277 | // 2.2.6.2 278 | [Fact] 279 | public void _when_promise_is_rejected_all_respective_onRejected_callbacks_must_execute_in_the_order_of_their_originating_calls_to_then() 280 | { 281 | var promise = new Promise(); 282 | 283 | var order = 0; 284 | 285 | promise.Catch(_ => 286 | { 287 | Assert.Equal(1, ++order); 288 | }); 289 | promise.Catch(_ => 290 | { 291 | Assert.Equal(2, ++order); 292 | }); 293 | promise.Catch(_ => 294 | { 295 | Assert.Equal(3, ++order); 296 | }); 297 | 298 | promise.Reject(new Exception()); 299 | 300 | Assert.Equal(3, order); 301 | } 302 | } 303 | 304 | // 2.2.7 305 | public class then_must_return_a_promise 306 | { 307 | 308 | // 2.2.7.1 309 | public class _If_either_onFulfilled_or_onRejected_returns_a_value_x_fulfill_promise_with_x 310 | { 311 | [Fact] 312 | public void _when_promise1_is_resolved() 313 | { 314 | var promise1 = new Promise(); 315 | 316 | var promisedValue1 = new object(); 317 | var promisedValue2 = new object(); 318 | 319 | var promise2 = 320 | promise1.Then(_ => promisedValue2); 321 | 322 | var promise1ThenHandler = 0; 323 | promise1.Then(v => 324 | { 325 | Assert.Equal(promisedValue1, v); 326 | ++promise1ThenHandler; 327 | 328 | }); 329 | 330 | var promise2ThenHandler = 0; 331 | promise2.Then(v => 332 | { 333 | Assert.Equal(promisedValue2, v); 334 | ++promise2ThenHandler; 335 | 336 | }); 337 | 338 | promise1.Resolve(promisedValue1); 339 | 340 | Assert.Equal(1, promise1ThenHandler); 341 | Assert.Equal(1, promise2ThenHandler); 342 | } 343 | 344 | [Fact] 345 | public void _when_promise1_is_rejected_with_no_value_in_catch() 346 | { 347 | var callbackInvoked = false; 348 | 349 | new Promise((res, rej) => rej(new Exception())) 350 | .Catch(_ => {}) 351 | .Then(() => callbackInvoked = true); 352 | 353 | Assert.True(callbackInvoked); 354 | } 355 | 356 | [Fact] 357 | public void _when_promise1_is_rejected_with_no_value_in_then() 358 | { 359 | var callbackInvoked = false; 360 | var resolveHandlerInvoked = false; 361 | var rejectHandlerInvoked = false; 362 | 363 | new Promise((res, rej) => rej(new Exception())) 364 | .Then( 365 | () => { resolveHandlerInvoked = true; }, 366 | ex => { rejectHandlerInvoked = true; } 367 | ) 368 | .Then(() => callbackInvoked = true); 369 | 370 | Assert.True(callbackInvoked); 371 | Assert.False(resolveHandlerInvoked); 372 | Assert.True(rejectHandlerInvoked); 373 | } 374 | 375 | [Fact] 376 | public void _when_promise1_is_rejected_with_value_in_catch() 377 | { 378 | var expectedValue = "Value returned from Catch"; 379 | var actualValue = string.Empty; 380 | 381 | new Promise((res, rej) => rej(new Exception())) 382 | .Catch(_ => expectedValue) 383 | .Then(val => actualValue = val); 384 | 385 | Assert.Equal(expectedValue, actualValue); 386 | } 387 | 388 | [Fact] 389 | public void _when_promise1_is_rejected_with_value_in_then() 390 | { 391 | var expectedValue = "Value returned from reject handler"; 392 | var actualValue = string.Empty; 393 | 394 | new Promise((res, rej) => rej(new Exception())) 395 | .Then( 396 | _ => Promise.Resolved(string.Empty), 397 | _ => Promise.Resolved(expectedValue) 398 | ) 399 | .Then(val => actualValue = val); 400 | 401 | Assert.Equal(expectedValue, actualValue); 402 | } 403 | 404 | [Fact] 405 | public void _when_non_generic_promise1_is_rejected() 406 | { 407 | var callbackInvoked = false; 408 | 409 | new Promise((res, rej) => rej(new Exception())) 410 | .Catch(_ => {}) 411 | .Then(() => callbackInvoked = true); 412 | 413 | Assert.True(callbackInvoked); 414 | } 415 | } 416 | 417 | // 2.2.7.2 418 | public class _if_either_onFulfilled_or_onRejected_throws_an_exception_e_promise2_must_be_rejected_with_e_as_the_reason 419 | { 420 | [Fact] 421 | public void _when_promise1_is_resolved_1() 422 | { 423 | var promise1 = new Promise(); 424 | 425 | var e = new Exception(); 426 | Func> thenHandler = _ => throw e; 427 | 428 | var promise2 = 429 | promise1.Then(thenHandler); 430 | 431 | promise1.Catch(_ => throw new Exception("This shouldn't happen!")); 432 | 433 | var errorHandledForPromise2 = 0; 434 | promise2.Catch(ex => 435 | { 436 | Assert.Equal(e, ex); 437 | 438 | ++errorHandledForPromise2; 439 | }); 440 | 441 | promise1.Resolve(new object()); 442 | 443 | Assert.Equal(1, errorHandledForPromise2); 444 | } 445 | 446 | [Fact] 447 | public void _when_promise1_is_resolved_2() 448 | { 449 | var promise1 = new Promise(); 450 | 451 | var e = new Exception(); 452 | Action thenHandler = _ => throw e; 453 | 454 | var promise2 = 455 | promise1.Then(thenHandler); 456 | 457 | promise1.Catch(_ => throw new Exception("This shouldn't happen!")); 458 | 459 | var errorHandledForPromise2 = 0; 460 | promise2.Catch(ex => 461 | { 462 | Assert.Equal(e, ex); 463 | 464 | ++errorHandledForPromise2; 465 | }); 466 | 467 | promise1.Resolve(new object()); 468 | 469 | Assert.Equal(1, errorHandledForPromise2); 470 | } 471 | 472 | [Fact] 473 | public void _when_promise1_is_rejected() 474 | { 475 | var promise1 = new Promise(); 476 | 477 | var e = new Exception(); 478 | var promise2 = 479 | promise1.Catch(_ => throw e); 480 | 481 | promise1.Catch(_ => throw new Exception("This shouldn't happen!")); 482 | 483 | var errorHandledForPromise2 = 0; 484 | promise2.Catch(ex => 485 | { 486 | Assert.Equal(e, ex); 487 | 488 | ++errorHandledForPromise2; 489 | }); 490 | 491 | promise1.Reject(new Exception()); 492 | 493 | Assert.Equal(1, errorHandledForPromise2); 494 | } 495 | } 496 | 497 | // 2.2.7.3 498 | [Fact] 499 | public void _If_onFulfilled_is_not_a_function_and_promise1_is_fulfilled_promise2_must_be_fulfilled_with_the_same_value_as_promise1() 500 | { 501 | var promise1 = new Promise(); 502 | 503 | var promise2 = promise1.Catch(_ => 504 | throw new Exception("There shouldn't be an error") 505 | ); 506 | 507 | var promisedValue = new object(); 508 | var promise2ThenHandler = 0; 509 | 510 | promise2.Then(v => 511 | { 512 | Assert.Equal(promisedValue, v); 513 | ++promise2ThenHandler; 514 | }); 515 | 516 | promise1.Resolve(promisedValue); 517 | 518 | Assert.Equal(1, promise2ThenHandler); 519 | } 520 | 521 | [Fact] 522 | public void _If_onRejected_is_not_a_function_and_promise1_is_rejected_promise2_must_be_rejected_with_the_same_reason_as_promise1() 523 | { 524 | var promise1 = new Promise(); 525 | 526 | var promise2 = promise1.Then(_ => 527 | throw new Exception("There shouldn't be a then callback") 528 | ); 529 | 530 | var e = new Exception(); 531 | var promise2CatchHandler = 0; 532 | 533 | promise2.Catch(ex => 534 | { 535 | Assert.Equal(e, ex); 536 | ++promise2CatchHandler; 537 | }); 538 | 539 | promise1.Reject(e); 540 | 541 | Assert.Equal(1, promise2CatchHandler); 542 | } 543 | } 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /Tests/Promise.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {4B4EE1A6-25A3-43D0-BAE9-5E45E31F2816} 9 | Library 10 | Properties 11 | RSG.Promise.Tests 12 | RSG.Promise.Tests 13 | v3.5 14 | 512 15 | 16 | ..\ 17 | true 18 | 19 | 20 | 21 | 22 | true 23 | full 24 | false 25 | ..\bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | ..\bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | False 41 | ..\packages\Moq.4.2.1409.1722\lib\net35\Moq.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ..\packages\xunit.1.9.2\lib\net20\xunit.dll 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | {7e1c6a8a-84e7-43c3-ae73-c6e8a9f11af3} 66 | RSG.Promise 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Designer 76 | 77 | 78 | 79 | 80 | 81 | 82 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 83 | 84 | 85 | 86 | 87 | 94 | -------------------------------------------------------------------------------- /Tests/PromiseProgressTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RSG.Exceptions; 3 | using Xunit; 4 | 5 | namespace RSG.Tests 6 | { 7 | public class PromiseProgressTests 8 | { 9 | [Fact] 10 | public void can_report_simple_progress() 11 | { 12 | const float expectedStep = 0.25f; 13 | var currentProgress = 0f; 14 | var promise = new Promise(); 15 | 16 | promise.Progress(v => 17 | { 18 | Assert.InRange(expectedStep - (v - currentProgress), -Math.E, Math.E); 19 | currentProgress = v; 20 | }); 21 | 22 | for (var progress = 0.25f; progress < 1f; progress += 0.25f) 23 | promise.ReportProgress(progress); 24 | promise.ReportProgress(1f); 25 | 26 | Assert.Equal(1f, currentProgress); 27 | } 28 | 29 | [Fact] 30 | public void can_handle_onProgress() 31 | { 32 | var promise = new Promise(); 33 | var progress = 0f; 34 | 35 | promise.Then(null, null, v => progress = v); 36 | 37 | promise.ReportProgress(1f); 38 | 39 | Assert.Equal(1f, progress); 40 | } 41 | 42 | [Fact] 43 | public void can_handle_chained_onProgress() 44 | { 45 | var promiseA = new Promise(); 46 | var promiseB = new Promise(); 47 | var progressA = 0f; 48 | var progressB = 0f; 49 | int result = 0; 50 | 51 | promiseA 52 | .Then(v => promiseB, null, v => progressA = v) 53 | .Progress(v => progressB = v) 54 | .Then(v => result = v) 55 | .Done(); 56 | 57 | promiseA.ReportProgress(1f); 58 | promiseA.Resolve(-17); 59 | promiseB.ReportProgress(2f); 60 | promiseB.Resolve(17); 61 | 62 | Assert.Equal(1f, progressA); 63 | Assert.Equal(2f, progressB); 64 | Assert.Equal(17, result); 65 | } 66 | 67 | [Fact] 68 | public void can_do_progress_weighted_average() 69 | { 70 | var promiseA = new Promise(); 71 | var promiseB = new Promise(); 72 | var promiseC = new Promise(); 73 | 74 | var expectedProgress = new[] { 0.1f, 0.2f, 0.6f, 1f }; 75 | var currentProgress = 0f; 76 | int currentStep = 0; 77 | int result = 0; 78 | 79 | promiseC. 80 | Progress(v => 81 | { 82 | Assert.InRange(currentStep, 0, expectedProgress.Length - 1); 83 | Assert.Equal(v, expectedProgress[currentStep]); 84 | currentProgress = v; 85 | ++currentStep; 86 | }) 87 | .Then(v => result = v) 88 | .Done() 89 | ; 90 | 91 | promiseA. 92 | Progress(v => promiseC.ReportProgress(v * 0.2f)) 93 | .Then(v => promiseB, null) 94 | .Progress(v => promiseC.ReportProgress(0.2f + 0.8f * v)) 95 | .Then(v => promiseC.Resolve(v)) 96 | .Catch(ex => promiseC.Reject(ex)) 97 | ; 98 | 99 | promiseA.ReportProgress(0.5f); 100 | promiseA.ReportProgress(1f); 101 | promiseA.Resolve(-17); 102 | promiseB.ReportProgress(0.5f); 103 | promiseB.ReportProgress(1f); 104 | promiseB.Resolve(17); 105 | 106 | Assert.Equal(expectedProgress.Length, currentStep); 107 | Assert.Equal(1f, currentProgress); 108 | Assert.Equal(17, result); 109 | } 110 | 111 | 112 | [Fact] 113 | public void chain_multiple_promises_reporting_progress() 114 | { 115 | var promiseA = new Promise(); 116 | var promiseB = new Promise(); 117 | var progressA = 0f; 118 | var progressB = 0f; 119 | int result = 0; 120 | 121 | promiseA 122 | .Progress(v => progressA = v) 123 | .Then(v => promiseB, null) 124 | .Progress(v => progressB = v) 125 | .Then(v => result = v) 126 | .Done() 127 | ; 128 | 129 | promiseA.ReportProgress(1f); 130 | promiseA.Resolve(-17); 131 | promiseB.ReportProgress(2f); 132 | promiseB.Resolve(17); 133 | 134 | Assert.Equal(1f, progressA); 135 | Assert.Equal(2f, progressB); 136 | Assert.Equal(17, result); 137 | } 138 | 139 | [Fact] 140 | public void exception_is_thrown_for_progress_after_resolve() 141 | { 142 | var promise = new Promise(); 143 | promise.Resolve(17); 144 | 145 | Assert.Throws(() => promise.ReportProgress(1f)); 146 | } 147 | 148 | [Fact] 149 | public void exception_is_thrown_for_progress_after_reject() 150 | { 151 | var promise = new Promise(); 152 | promise.Reject(new Exception()); 153 | 154 | Assert.Throws(() => promise.ReportProgress(1f)); 155 | } 156 | 157 | [Fact] 158 | public void first_progress_is_averaged() 159 | { 160 | var promiseA = new Promise(); 161 | var promiseB = new Promise(); 162 | var promiseC = new Promise(); 163 | var promiseD = new Promise(); 164 | 165 | int currentStep = 0; 166 | var expectedProgress = new[] { 0.25f, 0.50f, 0.75f, 1f }; 167 | 168 | Promise.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 | 184 | [Fact] 185 | public void all_progress_is_averaged() 186 | { 187 | var promiseA = new Promise(); 188 | var promiseB = new Promise(); 189 | var promiseC = new Promise(); 190 | var promiseD = new Promise(); 191 | 192 | int currentStep = 0; 193 | var expectedProgress = new[] { 0.25f, 0.50f, 0.75f, 1f }; 194 | 195 | Promise.All(promiseA, promiseB, promiseC, promiseD) 196 | .Progress(progress => 197 | { 198 | Assert.InRange(currentStep, 0, expectedProgress.Length - 1); 199 | Assert.Equal(expectedProgress[currentStep], progress); 200 | ++currentStep; 201 | }); 202 | 203 | promiseA.ReportProgress(1f); 204 | promiseC.ReportProgress(1f); 205 | promiseB.ReportProgress(1f); 206 | promiseD.ReportProgress(1f); 207 | 208 | Assert.Equal(expectedProgress.Length, currentStep); 209 | } 210 | 211 | [Fact] 212 | public void race_progress_is_maxed() 213 | { 214 | var promiseA = new Promise(); 215 | var promiseB = new Promise(); 216 | int reportCount = 0; 217 | 218 | Promise.Race(promiseA, promiseB) 219 | .Progress(progress => 220 | { 221 | Assert.Equal(progress, 0.5f); 222 | ++reportCount; 223 | }); 224 | 225 | promiseA.ReportProgress(0.5f); 226 | promiseB.ReportProgress(0.1f); 227 | promiseB.ReportProgress(0.2f); 228 | promiseB.ReportProgress(0.3f); 229 | promiseB.ReportProgress(0.4f); 230 | promiseB.ReportProgress(0.5f); 231 | 232 | Assert.Equal(6, reportCount); 233 | } 234 | 235 | [Fact] 236 | public void all_progress_with_resolved() 237 | { 238 | var promiseA = new Promise(); 239 | var promiseB = Promise.Resolved(17); 240 | int reportedCount = 0; 241 | 242 | Promise.All(promiseA, promiseB) 243 | .Progress(progress => 244 | { 245 | ++reportedCount; 246 | Assert.Equal(0.75f, progress); 247 | }); 248 | 249 | promiseA.ReportProgress(0.5f); 250 | 251 | Assert.Equal(1, reportedCount); 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /Tests/PromiseTimerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace RSG.Tests 5 | { 6 | public class PromiseTimerTests 7 | { 8 | [Fact] 9 | public void wait_until_elapsedUpdates_resolves_when_predicate_is_true() 10 | { 11 | var testObject = new PromiseTimer(); 12 | 13 | const int testFrame = 3; 14 | var hasResolved = false; 15 | 16 | testObject.WaitUntil(timeData => timeData.elapsedUpdates == testFrame) 17 | .Then(() => hasResolved = true) 18 | .Done(); 19 | 20 | Assert.False(hasResolved); 21 | 22 | testObject.Update(1); 23 | testObject.Update(2); 24 | testObject.Update(3); 25 | 26 | Assert.True(hasResolved); 27 | } 28 | 29 | [Fact] 30 | public void wait_for_doesnt_resolve_before_specified_time() 31 | { 32 | var testObject = new PromiseTimer(); 33 | 34 | const float testTime = 2f; 35 | var hasResolved = false; 36 | 37 | testObject.WaitFor(testTime) 38 | .Then(() => hasResolved = true) 39 | .Done(); 40 | 41 | testObject.Update(1f); 42 | 43 | Assert.Equal(false, hasResolved); 44 | } 45 | 46 | [Fact] 47 | public void wait_for_resolves_after_specified_time() 48 | { 49 | var testObject = new PromiseTimer(); 50 | 51 | const float testTime = 1f; 52 | var hasResolved = false; 53 | 54 | testObject.WaitFor(testTime) 55 | .Then(() => hasResolved = true) 56 | .Done(); 57 | 58 | testObject.Update(2f); 59 | 60 | Assert.Equal(true, hasResolved); 61 | } 62 | 63 | [Fact] 64 | public void wait_until_resolves_when_predicate_is_true() 65 | { 66 | var testObject = new PromiseTimer(); 67 | 68 | var hasResolved = false; 69 | 70 | var doResolve = false; 71 | 72 | testObject.WaitUntil(timeData => doResolve) 73 | .Then(() => hasResolved = true) 74 | .Done(); 75 | 76 | Assert.Equal(false, hasResolved); 77 | 78 | doResolve = true; 79 | testObject.Update(1f); 80 | 81 | Assert.Equal(true, hasResolved); 82 | } 83 | 84 | [Fact] 85 | public void wait_while_resolves_when_predicate_is_false() 86 | { 87 | var testObject = new PromiseTimer(); 88 | 89 | var hasResovled = false; 90 | 91 | var doWait = true; 92 | 93 | testObject.WaitWhile(timeData => doWait) 94 | .Then(() => hasResovled = true) 95 | .Done(); 96 | 97 | Assert.Equal(false, hasResovled); 98 | 99 | doWait = false; 100 | testObject.Update(1f); 101 | 102 | Assert.Equal(true, hasResovled); 103 | } 104 | 105 | [Fact] 106 | public void predicate_is_removed_from_timer_after_exception_is_thrown() 107 | { 108 | var testObject = new PromiseTimer(); 109 | 110 | var runCount = 0; 111 | 112 | testObject 113 | .WaitUntil(timeData => 114 | { 115 | runCount++; 116 | 117 | throw new NotImplementedException(); 118 | }) 119 | .Done(); 120 | 121 | testObject.Update(1.0f); 122 | testObject.Update(1.0f); 123 | 124 | Assert.Equal(1, runCount); 125 | } 126 | 127 | [Fact] 128 | public void when_promise_is_not_cancelled_by_user_resolve_promise() 129 | { 130 | var testObject = new PromiseTimer(); 131 | var hasResolved = false; 132 | Exception caughtException = null; 133 | 134 | 135 | var promise = testObject 136 | .WaitUntil(timeData => timeData.elapsedTime > 1.0f) 137 | .Then(() => hasResolved = true) 138 | .Catch(ex => caughtException = ex); 139 | 140 | promise.Done(null, ex => caughtException = ex); 141 | 142 | testObject.Update(1.0f); 143 | 144 | Assert.Equal(hasResolved, false); 145 | 146 | testObject.Update(1.0f); 147 | 148 | Assert.Equal(caughtException, null); 149 | Assert.Equal(hasResolved, true); 150 | } 151 | 152 | [Fact] 153 | public void when_promise_is_cancelled_by_user_reject_promise() 154 | { 155 | var testObject = new PromiseTimer(); 156 | Exception caughtException = null; 157 | 158 | 159 | var promise = testObject 160 | .WaitUntil(timeData => timeData.elapsedTime > 1.0f); 161 | promise.Catch(ex => caughtException = ex); 162 | 163 | promise.Done(null, ex => caughtException = ex); 164 | 165 | testObject.Update(1.0f); 166 | testObject.Cancel(promise); 167 | testObject.Update(1.0f); 168 | 169 | Assert.IsType(caughtException); 170 | Assert.Equal(caughtException.Message, "Promise was cancelled by user."); 171 | } 172 | 173 | [Fact] 174 | public void when_predicate_throws_exception_reject_promise() 175 | { 176 | var testObject = new PromiseTimer(); 177 | 178 | Exception expectedException = new Exception(); 179 | Exception caughtException = null; 180 | 181 | 182 | testObject 183 | .WaitUntil(timeData => throw expectedException) 184 | .Catch(ex => caughtException = ex) 185 | .Done(); 186 | 187 | testObject.Update(1.0f); 188 | 189 | Assert.Equal(expectedException, caughtException); 190 | } 191 | 192 | [Fact] 193 | public void all_promises_are_updated_when_a_pending_promise_is_resolved_during_update() 194 | { 195 | var testObject = new PromiseTimer(); 196 | 197 | var p1Updates = 0; 198 | var p2Updates = 0; 199 | var p3Updates = 0; 200 | 201 | testObject 202 | .WaitUntil(timeData => 203 | { 204 | p1Updates++; 205 | 206 | return false; 207 | }); 208 | 209 | testObject 210 | .WaitUntil(timeData => 211 | { 212 | p2Updates++; 213 | 214 | return true; 215 | }); 216 | 217 | testObject 218 | .WaitUntil(timeData => 219 | { 220 | p3Updates++; 221 | 222 | return false; 223 | }); 224 | 225 | testObject.Update(0.01f); 226 | 227 | Assert.Equal(1, p1Updates); 228 | Assert.Equal(1, p2Updates); 229 | Assert.Equal(1, p3Updates); 230 | } 231 | 232 | [Fact] 233 | public void all_promises_are_updated_when_a_pending_promise_is_canceled_during_update() 234 | { 235 | var testObject = new PromiseTimer(); 236 | 237 | var p1Updates = 0; 238 | var p2Updates = 0; 239 | var p3Updates = 0; 240 | 241 | var p1 = testObject 242 | .WaitUntil(timeData => 243 | { 244 | p1Updates++; 245 | 246 | return false; 247 | }); 248 | 249 | testObject 250 | .WaitUntil(timeData => 251 | { 252 | p2Updates++; 253 | 254 | return true; 255 | }) 256 | .Then(() => 257 | { 258 | testObject.Cancel(p1); 259 | }); 260 | 261 | testObject 262 | .WaitUntil(timeData => 263 | { 264 | p3Updates++; 265 | 266 | return false; 267 | }); 268 | 269 | testObject.Update(0.01f); 270 | 271 | Assert.Equal(1, p1Updates); 272 | Assert.Equal(1, p2Updates); 273 | Assert.Equal(1, p3Updates); 274 | } 275 | } 276 | } -------------------------------------------------------------------------------- /Tests/Promise_NonGeneric_ProgressTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RSG.Exceptions; 3 | using Xunit; 4 | 5 | namespace RSG.Tests 6 | { 7 | public class Promise_NonGeneric_ProgressTests 8 | { 9 | [Fact] 10 | public void can_report_simple_progress() 11 | { 12 | const float expectedStep = 0.25f; 13 | var currentProgress = 0f; 14 | var promise = new Promise(); 15 | 16 | promise.Progress(v => 17 | { 18 | Assert.InRange(expectedStep - (v - currentProgress), -Math.E, Math.E); 19 | currentProgress = v; 20 | }); 21 | 22 | for (var progress = 0.25f; progress < 1f; progress += 0.25f) 23 | promise.ReportProgress(progress); 24 | promise.ReportProgress(1f); 25 | 26 | Assert.Equal(1f, currentProgress); 27 | } 28 | 29 | [Fact] 30 | public void can_handle_onProgress() 31 | { 32 | var promise = new Promise(); 33 | var progress = 0f; 34 | 35 | promise.Then(null, null, v => progress = v); 36 | 37 | promise.ReportProgress(1f); 38 | 39 | Assert.Equal(1f, progress); 40 | } 41 | 42 | [Fact] 43 | public void can_handle_chained_onProgress() 44 | { 45 | var promiseA = new Promise(); 46 | var promiseB = new Promise(); 47 | var progressA = 0f; 48 | var progressB = 0f; 49 | 50 | promiseA 51 | .Then(() => promiseB, null, v => progressA = v) 52 | .Progress(v => progressB = v) 53 | .Done(); 54 | 55 | promiseA.ReportProgress(1f); 56 | promiseA.Resolve(); 57 | promiseB.ReportProgress(2f); 58 | promiseB.Resolve(); 59 | 60 | Assert.Equal(1f, progressA); 61 | Assert.Equal(2f, progressB); 62 | } 63 | 64 | [Fact] 65 | public void can_do_progress_weighted_average() 66 | { 67 | var promiseA = new Promise(); 68 | var promiseB = new Promise(); 69 | var promiseC = new Promise(); 70 | 71 | var expectedProgress = new[] { 0.1f, 0.2f, 0.6f, 1f }; 72 | var currentProgress = 0f; 73 | int currentStep = 0; 74 | 75 | promiseC. 76 | Progress(v => 77 | { 78 | Assert.InRange(currentStep, 0, expectedProgress.Length - 1); 79 | Assert.Equal(v, expectedProgress[currentStep]); 80 | currentProgress = v; 81 | ++currentStep; 82 | }) 83 | ; 84 | 85 | promiseA. 86 | Progress(v => promiseC.ReportProgress(v * 0.2f)) 87 | .Then(() => promiseB) 88 | .Progress(v => promiseC.ReportProgress(0.2f + 0.8f * v)) 89 | .Then(() => promiseC.Resolve()) 90 | .Catch(ex => promiseC.Reject(ex)) 91 | ; 92 | 93 | promiseA.ReportProgress(0.5f); 94 | promiseA.ReportProgress(1f); 95 | promiseA.Resolve(); 96 | promiseB.ReportProgress(0.5f); 97 | promiseB.ReportProgress(1f); 98 | promiseB.Resolve(); 99 | 100 | Assert.Equal(expectedProgress.Length, currentStep); 101 | Assert.Equal(1f, currentProgress); 102 | } 103 | 104 | 105 | [Fact] 106 | public void chain_multiple_promises_reporting_progress() 107 | { 108 | var promiseA = new Promise(); 109 | var promiseB = new Promise(); 110 | var progressA = 0f; 111 | var progressB = 0f; 112 | 113 | promiseA 114 | .Progress(v => progressA = v) 115 | .Then(() => promiseB) 116 | .Progress(v => progressB = v) 117 | .Done(); 118 | 119 | promiseA.ReportProgress(1f); 120 | promiseA.Resolve(); 121 | promiseB.ReportProgress(2f); 122 | promiseB.Resolve(); 123 | 124 | Assert.Equal(1f, progressA); 125 | Assert.Equal(2f, progressB); 126 | } 127 | 128 | [Fact] 129 | public void exception_is_thrown_for_progress_after_resolve() 130 | { 131 | var promise = new Promise(); 132 | promise.Resolve(); 133 | 134 | Assert.Throws(() => promise.ReportProgress(1f)); 135 | } 136 | 137 | [Fact] 138 | public void exception_is_thrown_for_progress_after_reject() 139 | { 140 | var promise = new Promise(); 141 | promise.Reject(new Exception()); 142 | 143 | Assert.Throws(() => promise.ReportProgress(1f)); 144 | } 145 | 146 | [Fact] 147 | public void all_progress_is_averaged() 148 | { 149 | var promiseA = new Promise(); 150 | var promiseB = new Promise(); 151 | var promiseC = new Promise(); 152 | var promiseD = new Promise(); 153 | 154 | int currentStep = 0; 155 | var expectedProgress = new[] { 0.25f, 0.50f, 0.75f, 1f }; 156 | 157 | Promise.All(promiseA, promiseB, promiseC, promiseD) 158 | .Progress(progress => 159 | { 160 | Assert.InRange(currentStep, 0, expectedProgress.Length - 1); 161 | Assert.Equal(expectedProgress[currentStep], progress); 162 | ++currentStep; 163 | }); 164 | 165 | promiseA.ReportProgress(1f); 166 | promiseC.ReportProgress(1f); 167 | promiseB.ReportProgress(1f); 168 | promiseD.ReportProgress(1f); 169 | 170 | Assert.Equal(expectedProgress.Length, currentStep); 171 | Assert.Equal(expectedProgress.Length, currentStep); 172 | } 173 | 174 | [Fact] 175 | public void race_progress_is_maxed() 176 | { 177 | var promiseA = new Promise(); 178 | var promiseB = new Promise(); 179 | int reportCount = 0; 180 | 181 | Promise.Race(promiseA, promiseB) 182 | .Progress(progress => 183 | { 184 | Assert.Equal(progress, 0.5f); 185 | ++reportCount; 186 | }); 187 | 188 | promiseA.ReportProgress(0.5f); 189 | promiseB.ReportProgress(0.1f); 190 | promiseB.ReportProgress(0.2f); 191 | promiseB.ReportProgress(0.3f); 192 | promiseB.ReportProgress(0.4f); 193 | promiseB.ReportProgress(0.5f); 194 | 195 | Assert.Equal(6, reportCount); 196 | } 197 | 198 | [Fact] 199 | public void all_progress_with_resolved() 200 | { 201 | var promiseA = new Promise(); 202 | var promiseB = Promise.Resolved(); 203 | int reportedCount = 0; 204 | 205 | Promise.All(promiseA, promiseB) 206 | .Progress(progress => 207 | { 208 | ++reportedCount; 209 | Assert.Equal(0.75f, progress); 210 | }); 211 | 212 | promiseA.ReportProgress(0.5f); 213 | 214 | Assert.Equal(1, reportedCount); 215 | } 216 | 217 | [Fact] 218 | public void sequence_reports_progress() 219 | { 220 | var promiseA = new Promise(); 221 | var promiseB = new Promise(); 222 | var promiseC = Promise.Resolved(); 223 | var promiseD = new Promise(); 224 | int currentReport = 0; 225 | var expectedProgress = new[] { 0.125f, 0.25f, 0.25f, 0.3125f, 0.375f, 0.4375f, 0.5f, 0.75f, 0.875f, 1f }; 226 | 227 | Promise 228 | .Sequence(() => promiseA, () => promiseB, () => promiseC, () => promiseD) 229 | .Progress(v => 230 | { 231 | Assert.Equal(expectedProgress[currentReport], v); 232 | ++currentReport; 233 | }) 234 | .Done() 235 | ; 236 | 237 | promiseA.ReportProgress(0.5f); 238 | promiseA.ReportProgress(1f); 239 | promiseA.Resolve(); 240 | 241 | promiseB.ReportProgress(0.25f); 242 | promiseB.ReportProgress(0.5f); 243 | promiseB.ReportProgress(0.75f); 244 | promiseB.Resolve(); 245 | 246 | promiseD.ReportProgress(0.5f); 247 | promiseD.ReportProgress(1f); 248 | promiseD.Resolve(); 249 | 250 | Assert.Equal(expectedProgress.Length, currentReport); 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Promise.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Promise.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("fc754749-05b0-40d4-b235-ca3ffbe4ea0b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Tests/TestHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace RSG.Tests 5 | { 6 | internal static class TestHelpers 7 | { 8 | /// 9 | /// Helper function that checks that the given action doesn't trigger the 10 | /// unhandled exception handler. 11 | /// 12 | internal static void VerifyDoesntThrowUnhandledException(Action testAction) 13 | { 14 | Exception unhandledException = null; 15 | EventHandler handler = 16 | (sender, args) => unhandledException = args.Exception; 17 | Promise.UnhandledException += handler; 18 | 19 | try 20 | { 21 | testAction(); 22 | 23 | Assert.Null(unhandledException); 24 | } 25 | finally 26 | { 27 | Promise.UnhandledException -= handler; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/Moq.4.2.1409.1722/Moq.4.2.1409.1722.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/Moq.4.2.1409.1722/Moq.4.2.1409.1722.nupkg -------------------------------------------------------------------------------- /packages/Moq.4.2.1409.1722/lib/net35/Moq.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/Moq.4.2.1409.1722/lib/net35/Moq.dll -------------------------------------------------------------------------------- /packages/Moq.4.2.1409.1722/lib/net40/Moq.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/Moq.4.2.1409.1722/lib/net40/Moq.dll -------------------------------------------------------------------------------- /packages/Moq.4.2.1409.1722/lib/sl4/Moq.Silverlight.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/Moq.4.2.1409.1722/lib/sl4/Moq.Silverlight.dll -------------------------------------------------------------------------------- /packages/xunit.1.9.2/lib/net20/xunit.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.1.9.2/lib/net20/xunit.dll -------------------------------------------------------------------------------- /packages/xunit.1.9.2/lib/net20/xunit.dll.tdnet: -------------------------------------------------------------------------------- 1 | 2 | xUnit.net {0}.{1}.{2} build {3} 3 | xunit.runner.tdnet.dll 4 | Xunit.Runner.TdNet.TdNetRunner 5 | -------------------------------------------------------------------------------- /packages/xunit.1.9.2/lib/net20/xunit.runner.msbuild.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.1.9.2/lib/net20/xunit.runner.msbuild.dll -------------------------------------------------------------------------------- /packages/xunit.1.9.2/lib/net20/xunit.runner.tdnet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.1.9.2/lib/net20/xunit.runner.tdnet.dll -------------------------------------------------------------------------------- /packages/xunit.1.9.2/lib/net20/xunit.runner.utility.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.1.9.2/lib/net20/xunit.runner.utility.dll -------------------------------------------------------------------------------- /packages/xunit.1.9.2/xunit.1.9.2.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.1.9.2/xunit.1.9.2.nupkg -------------------------------------------------------------------------------- /packages/xunit.runner.visualstudio.0.99.9-build1021/build/_common/xunit.abstractions.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.runner.visualstudio.0.99.9-build1021/build/_common/xunit.abstractions.dll -------------------------------------------------------------------------------- /packages/xunit.runner.visualstudio.0.99.9-build1021/build/_common/xunit.runner.utility.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.runner.visualstudio.0.99.9-build1021/build/_common/xunit.runner.utility.dll -------------------------------------------------------------------------------- /packages/xunit.runner.visualstudio.0.99.9-build1021/build/_common/xunit.runner.visualstudio.testadapter.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.runner.visualstudio.0.99.9-build1021/build/_common/xunit.runner.visualstudio.testadapter.dll -------------------------------------------------------------------------------- /packages/xunit.runner.visualstudio.0.99.9-build1021/build/_common/xunit.runner.visualstudio.testadapter.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.runner.visualstudio.0.99.9-build1021/build/_common/xunit.runner.visualstudio.testadapter.pdb -------------------------------------------------------------------------------- /packages/xunit.runner.visualstudio.0.99.9-build1021/build/net20/xunit.runner.visualstudio.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | PreserveNewest 6 | False 7 | 8 | 9 | PreserveNewest 10 | False 11 | 12 | 13 | PreserveNewest 14 | False 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/xunit.runner.visualstudio.0.99.9-build1021/build/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.runner.visualstudio.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | PreserveNewest 6 | False 7 | 8 | 9 | PreserveNewest 10 | False 11 | 12 | 13 | PreserveNewest 14 | False 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/xunit.runner.visualstudio.0.99.9-build1021/lib/net20/_._: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.runner.visualstudio.0.99.9-build1021/lib/net20/_._ -------------------------------------------------------------------------------- /packages/xunit.runner.visualstudio.0.99.9-build1021/lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/_._: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.runner.visualstudio.0.99.9-build1021/lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/_._ -------------------------------------------------------------------------------- /packages/xunit.runner.visualstudio.0.99.9-build1021/xunit.runner.visualstudio.0.99.9-build1021.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/packages/xunit.runner.visualstudio.0.99.9-build1021/xunit.runner.visualstudio.0.99.9-build1021.nupkg -------------------------------------------------------------------------------- /src/C-Sharp-Promise.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/src/C-Sharp-Promise.snk -------------------------------------------------------------------------------- /src/EnumerableExt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RSG.Promises 5 | { 6 | /// 7 | /// General extensions to LINQ. 8 | /// 9 | public static class EnumerableExt 10 | { 11 | public static void Each(this IEnumerable source, Action fn) 12 | { 13 | foreach (var item in source) 14 | { 15 | fn.Invoke(item); 16 | } 17 | } 18 | 19 | public static void Each(this IEnumerable source, Action fn) 20 | { 21 | int index = 0; 22 | 23 | foreach (T item in source) 24 | { 25 | fn.Invoke(item, index); 26 | index++; 27 | } 28 | } 29 | 30 | /// 31 | /// Convert a variable length argument list of items to an enumerable. 32 | /// 33 | public static IEnumerable FromItems(params T[] items) 34 | { 35 | foreach (var item in items) 36 | { 37 | yield return item; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Exceptions/PromiseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace RSG.Exceptions 7 | { 8 | /// 9 | /// Base class for promise exceptions. 10 | /// 11 | public class PromiseException : Exception 12 | { 13 | public PromiseException() { } 14 | 15 | public PromiseException(string message) : base(message) { } 16 | 17 | public PromiseException(string message, Exception inner) : base(message, inner) { } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Exceptions/PromiseStateException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace RSG.Exceptions 7 | { 8 | /// 9 | /// Exception thrown when an operation is performed on a promise that is in an invalid 10 | /// state for it to handle. 11 | /// 12 | public class PromiseStateException : PromiseException 13 | { 14 | public PromiseStateException() { } 15 | 16 | public PromiseStateException(string message) : base(message) { } 17 | 18 | public PromiseStateException(string message, Exception inner) 19 | : base(message, inner) 20 | { } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/PromiseHelpers.cs: -------------------------------------------------------------------------------- 1 | namespace RSG 2 | { 3 | public static class PromiseHelpers 4 | { 5 | /// 6 | /// Returns a promise that resolves with all of the specified promises have resolved. 7 | /// Returns a promise of a tuple of the resolved results. 8 | /// 9 | public static IPromise> All(IPromise p1, IPromise p2) 10 | { 11 | var val1 = default(T1); 12 | var val2 = default(T2); 13 | var numUnresolved = 2; 14 | var alreadyRejected = false; 15 | var promise = new Promise>(); 16 | 17 | p1 18 | .Then(val => 19 | { 20 | val1 = val; 21 | numUnresolved--; 22 | if (numUnresolved <= 0) 23 | { 24 | promise.Resolve(Tuple.Create(val1, val2)); 25 | } 26 | }) 27 | .Catch(e => 28 | { 29 | if (!alreadyRejected) 30 | { 31 | promise.Reject(e); 32 | } 33 | 34 | alreadyRejected = true; 35 | }) 36 | .Done(); 37 | 38 | p2 39 | .Then(val => 40 | { 41 | val2 = val; 42 | numUnresolved--; 43 | if (numUnresolved <= 0) 44 | { 45 | promise.Resolve(Tuple.Create(val1, val2)); 46 | } 47 | }) 48 | .Catch(e => 49 | { 50 | if (!alreadyRejected) 51 | { 52 | promise.Reject(e); 53 | } 54 | 55 | alreadyRejected = true; 56 | }) 57 | .Done(); 58 | 59 | return promise; 60 | } 61 | 62 | /// 63 | /// Returns a promise that resolves with all of the specified promises have resolved. 64 | /// Returns a promise of a tuple of the resolved results. 65 | /// 66 | public static IPromise> All(IPromise p1, IPromise p2, IPromise p3) 67 | { 68 | return All(All(p1, p2), p3) 69 | .Then(vals => Tuple.Create(vals.Item1.Item1, vals.Item1.Item2, vals.Item2)); 70 | } 71 | 72 | /// 73 | /// Returns a promise that resolves with all of the specified promises have resolved. 74 | /// Returns a promise of a tuple of the resolved results. 75 | /// 76 | public static IPromise> All(IPromise p1, IPromise p2, IPromise p3, IPromise p4) 77 | { 78 | return All(All(p1, p2), All(p3, p4)) 79 | .Then(vals => Tuple.Create(vals.Item1.Item1, vals.Item1.Item2, vals.Item2.Item1, vals.Item2.Item2)); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/PromiseTimer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RSG 5 | { 6 | 7 | public class PromiseCancelledException : Exception 8 | { 9 | /// 10 | /// Just create the exception 11 | /// 12 | public PromiseCancelledException() 13 | { 14 | 15 | } 16 | 17 | /// 18 | /// Create the exception with description 19 | /// 20 | /// Exception description 21 | public PromiseCancelledException(String message) : base(message) 22 | { 23 | 24 | } 25 | } 26 | 27 | /// 28 | /// A class that wraps a pending promise with it's predicate and time data 29 | /// 30 | internal class PredicateWait 31 | { 32 | /// 33 | /// Predicate for resolving the promise 34 | /// 35 | public Func predicate; 36 | 37 | /// 38 | /// The time the promise was started 39 | /// 40 | public float timeStarted; 41 | 42 | /// 43 | /// The pending promise which is an interface for a promise that can be rejected or resolved. 44 | /// 45 | public IPendingPromise pendingPromise; 46 | 47 | /// 48 | /// The time data specific to this pending promise. Includes elapsed time and delta time. 49 | /// 50 | public TimeData timeData; 51 | 52 | /// 53 | /// The frame the promise was started 54 | /// 55 | public int frameStarted; 56 | } 57 | 58 | /// 59 | /// Time data specific to a particular pending promise. 60 | /// 61 | public struct TimeData 62 | { 63 | /// 64 | /// The amount of time that has elapsed since the pending promise started running 65 | /// 66 | public float elapsedTime; 67 | 68 | /// 69 | /// The amount of time since the last time the pending promise was updated. 70 | /// 71 | public float deltaTime; 72 | 73 | /// 74 | /// The amount of times that update has been called since the pending promise started running 75 | /// 76 | public int elapsedUpdates; 77 | } 78 | 79 | public interface IPromiseTimer 80 | { 81 | /// 82 | /// Resolve the returned promise once the time has elapsed 83 | /// 84 | IPromise WaitFor(float seconds); 85 | 86 | /// 87 | /// Resolve the returned promise once the predicate evaluates to true 88 | /// 89 | IPromise WaitUntil(Func predicate); 90 | 91 | /// 92 | /// Resolve the returned promise once the predicate evaluates to false 93 | /// 94 | IPromise WaitWhile(Func predicate); 95 | 96 | /// 97 | /// Update all pending promises. Must be called for the promises to progress and resolve at all. 98 | /// 99 | void Update(float deltaTime); 100 | 101 | /// 102 | /// Cancel a waiting promise and reject it immediately. 103 | /// 104 | bool Cancel(IPromise promise); 105 | } 106 | 107 | public class PromiseTimer : IPromiseTimer 108 | { 109 | /// 110 | /// The current running total for time that this PromiseTimer has run for 111 | /// 112 | private float curTime; 113 | 114 | /// 115 | /// The current running total for the amount of frames the PromiseTimer has run for 116 | /// 117 | private int curFrame; 118 | 119 | /// 120 | /// Currently pending promises 121 | /// 122 | private readonly LinkedList waiting = new LinkedList(); 123 | 124 | /// 125 | /// Resolve the returned promise once the time has elapsed 126 | /// 127 | public IPromise WaitFor(float seconds) 128 | { 129 | return WaitUntil(t => t.elapsedTime >= seconds); 130 | } 131 | 132 | /// 133 | /// Resolve the returned promise once the predicate evaluates to false 134 | /// 135 | public IPromise WaitWhile(Func predicate) 136 | { 137 | return WaitUntil(t => !predicate(t)); 138 | } 139 | 140 | /// 141 | /// Resolve the returned promise once the predicate evalutes to true 142 | /// 143 | public IPromise WaitUntil(Func predicate) 144 | { 145 | var promise = new Promise(); 146 | 147 | var wait = new PredicateWait() 148 | { 149 | timeStarted = curTime, 150 | pendingPromise = promise, 151 | timeData = new TimeData(), 152 | predicate = predicate, 153 | frameStarted = curFrame 154 | }; 155 | 156 | waiting.AddLast(wait); 157 | 158 | return promise; 159 | } 160 | 161 | public bool Cancel(IPromise promise) 162 | { 163 | var node = FindInWaiting(promise); 164 | 165 | if (node == null) 166 | { 167 | return false; 168 | } 169 | 170 | node.Value.pendingPromise.Reject(new PromiseCancelledException("Promise was cancelled by user.")); 171 | waiting.Remove(node); 172 | 173 | return true; 174 | } 175 | 176 | LinkedListNode FindInWaiting(IPromise promise) 177 | { 178 | for (var node = waiting.First; node != null; node = node.Next) 179 | { 180 | if (node.Value.pendingPromise.Id.Equals(promise.Id)) 181 | { 182 | return node; 183 | } 184 | } 185 | 186 | return null; 187 | } 188 | 189 | /// 190 | /// Update all pending promises. Must be called for the promises to progress and resolve at all. 191 | /// 192 | public void Update(float deltaTime) 193 | { 194 | curTime += deltaTime; 195 | curFrame += 1; 196 | 197 | var node = waiting.First; 198 | while (node != null) 199 | { 200 | var wait = node.Value; 201 | 202 | var newElapsedTime = curTime - wait.timeStarted; 203 | wait.timeData.deltaTime = newElapsedTime - wait.timeData.elapsedTime; 204 | wait.timeData.elapsedTime = newElapsedTime; 205 | var newElapsedUpdates = curFrame - wait.frameStarted; 206 | wait.timeData.elapsedUpdates = newElapsedUpdates; 207 | 208 | bool result; 209 | try 210 | { 211 | result = wait.predicate(wait.timeData); 212 | } 213 | catch (Exception ex) 214 | { 215 | wait.pendingPromise.Reject(ex); 216 | 217 | node = RemoveNode(node); 218 | continue; 219 | } 220 | 221 | if (result) 222 | { 223 | wait.pendingPromise.Resolve(); 224 | 225 | node = RemoveNode(node); 226 | } 227 | else 228 | { 229 | node = node.Next; 230 | } 231 | } 232 | } 233 | 234 | /// 235 | /// Removes the provided node and returns the next node in the list. 236 | /// 237 | private LinkedListNode RemoveNode(LinkedListNode node) 238 | { 239 | var currentNode = node; 240 | node = node.Next; 241 | 242 | waiting.Remove(currentNode); 243 | 244 | return node; 245 | } 246 | } 247 | } 248 | 249 | -------------------------------------------------------------------------------- /src/RSG.Promise.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net35;netstandard2.0 4 | 3.0.1 5 | Real Serious Games 6 | Promises library for C# for management of asynchronous operations. 7 | Copyright © Real Serious Games 2018 8 | https://opensource.org/licenses/MIT 9 | https://github.com/Real-Serious-Games/C-Sharp-Promise 10 | 11 | true 12 | true 13 | C-Sharp-Promise.snk 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Tuple.cs: -------------------------------------------------------------------------------- 1 | namespace RSG 2 | { 3 | /// 4 | /// Provides static methods for creating tuple objects. 5 | /// 6 | /// Tuple implementation for .NET 3.5 7 | /// 8 | public class Tuple 9 | { 10 | /// 11 | /// Create a new 2-tuple, or pair. 12 | /// 13 | /// The type of the first component of the tuple. 14 | /// The type of the second component of the tuple. 15 | /// The value of the first component of the tuple. 16 | /// The value of the second component of the tuple. 17 | /// A 2-tuple whose value is (item1, item2) 18 | public static Tuple Create(T1 item1, T2 item2) 19 | { 20 | return new Tuple(item1, item2); 21 | } 22 | 23 | /// 24 | /// Create a new 3-tuple, or triple. 25 | /// 26 | /// The type of the first component of the tuple. 27 | /// The type of the second component of the tuple. 28 | /// The type of the third component of the tuple. 29 | /// The value of the first component of the tuple. 30 | /// The value of the second component of the tuple. 31 | /// The value of the third component of the tuple. 32 | /// A 3-tuple whose value is (item1, item2, item3) 33 | public static Tuple Create(T1 item1, T2 item2, T3 item3) 34 | { 35 | return new Tuple(item1, item2, item3); 36 | } 37 | 38 | /// 39 | /// Create a new 4-tuple, or quadruple. 40 | /// 41 | /// The type of the first component of the tuple. 42 | /// The type of the second component of the tuple. 43 | /// The type of the third component of the tuple. 44 | /// The type of the fourth component of the tuple. 45 | /// The value of the first component of the tuple. 46 | /// The value of the second component of the tuple. 47 | /// The value of the third component of the tuple. 48 | /// The value of the fourth component of the tuple. 49 | /// A 3-tuple whose value is (item1, item2, item3, item4) 50 | public static Tuple Create(T1 item1, T2 item2, T3 item3, T4 item4) 51 | { 52 | return new Tuple(item1, item2, item3, item4); 53 | } 54 | } 55 | 56 | /// 57 | /// Represents a 2-tuple, or pair. 58 | /// 59 | /// The type of the tuple's first component. 60 | /// The type of the tuple's second component. 61 | public class Tuple 62 | { 63 | internal Tuple(T1 item1, T2 item2) 64 | { 65 | Item1 = item1; 66 | Item2 = item2; 67 | } 68 | 69 | /// 70 | /// Gets the value of the current tuple's first component. 71 | /// 72 | public T1 Item1 { get; private set; } 73 | 74 | /// 75 | /// Gets the value of the current tuple's second component. 76 | /// 77 | public T2 Item2 { get; private set; } 78 | } 79 | 80 | /// 81 | /// Represents a 3-tuple, or triple. 82 | /// 83 | /// The type of the tuple's first component. 84 | /// The type of the tuple's second component. 85 | /// The type of the tuple's third component. 86 | public class Tuple 87 | { 88 | internal Tuple(T1 item1, T2 item2, T3 item3) 89 | { 90 | Item1 = item1; 91 | Item2 = item2; 92 | Item3 = item3; 93 | } 94 | 95 | /// 96 | /// Gets the value of the current tuple's first component. 97 | /// 98 | public T1 Item1 { get; private set; } 99 | 100 | /// 101 | /// Gets the value of the current tuple's second component. 102 | /// 103 | public T2 Item2 { get; private set; } 104 | 105 | /// 106 | /// Gets the value of the current tuple's third component. 107 | /// 108 | public T3 Item3 { get; private set; } 109 | } 110 | 111 | /// 112 | /// Represents a 4-tuple, or quadruple. 113 | /// 114 | /// The type of the tuple's first component. 115 | /// The type of the tuple's second component. 116 | /// The type of the tuple's third component. 117 | /// The type of the tuple's fourth component. 118 | public class Tuple 119 | { 120 | internal Tuple(T1 item1, T2 item2, T3 item3, T4 item4) 121 | { 122 | Item1 = item1; 123 | Item2 = item2; 124 | Item3 = item3; 125 | Item4 = item4; 126 | } 127 | 128 | /// 129 | /// Gets the value of the current tuple's first component. 130 | /// 131 | public T1 Item1 { get; private set; } 132 | 133 | /// 134 | /// Gets the value of the current tuple's second component. 135 | /// 136 | public T2 Item2 { get; private set; } 137 | 138 | /// 139 | /// Gets the value of the current tuple's third component. 140 | /// 141 | public T3 Item3 { get; private set; } 142 | 143 | /// 144 | /// Gets the value of the current tuple's fourth component. 145 | /// 146 | public T4 Item4 { get; private set; } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /xunit/xunit.console.clr4.x86.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/xunit/xunit.console.clr4.x86.exe -------------------------------------------------------------------------------- /xunit/xunit.runner.utility.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Serious-Games/C-Sharp-Promise/824b714b47e40239aa6595a3288460c461539345/xunit/xunit.runner.utility.dll --------------------------------------------------------------------------------