Promises.html 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. <html>
  2. <head>
  3. <title>C# Promises</title>
  4. <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABL1BMVEUAAAAAAAAAAAAAAAAAAAAQAAABBBwZAAAAFAUUAAAADwMAAAAAAAAAAAAAAAAAAAAAAAAABgIuAAAsAAAANQwAGAUAAg0AAAAAAAAAAAAAAAAAAAAAwzIAvzAEOvwEPP8ESP0EQ/0EP/0ABQvtAAADMfgAHpkA1DkAzjcAyzYAyDUAuC0AaBj4AADyAADpAADmAADjAADWAADLAABAAAAFS/8DNP8FTf4DNvwEOPYDK/ICIroCEF0A0DkAvC8AnyYAnCUAkCIAdxwAAhgAQQ8bBAECBAB4AQD7AADwAADfAADcAAC5AAC0AACIAABOAAADMf8DJcwCIbACHaMCGYsBD0EBDTYAlSEAjSEASREASBEAAw8TAgwAMQsCJQkALwgAQAT0AADhAABjAABeAABD9A36AAAAHHRSTlMAGK5/eP7B1syvpoloYFUTAfnr6ujkvaaUb10gmPaQKAAAAOZJREFUGNM1j9dWwlAQRSeFSBGw13tBTQA1HSLSpPdmV3r//29gILDfzj7rrDUDGxif2+l0+xjYcgT8WbGQyxWUc942l9XqpCfLWUrLHkA4q9NsWwqlhJCyF/cnpqrWpUqekOen7DEDN11N0+b1WhHzY2TAg6stSY2GWlMwv0X6LBzEdF2XjMqYYqbiBQrTMGJWqTTLy//TbwFcneYiOkonk+k/8fczxYK32zLfwy939w/BePzjhwfmtBV1BF9RBBKJlOjHw5Z7EQhlDgG5Wu0moYzH/u7aIX5hHR5ib3PLsYLAcn5A1tqnJNnWRf14AAAAAElFTkSuQmCC">
  5. <style type="text/css">
  6. body {
  7. font-family: sans-serif;
  8. margin: 0;
  9. padding: 0;
  10. }
  11. * { box-sizing: border-box; }
  12. #title {
  13. position: absolute;
  14. top: 0; left: 0; right: 0; height: 40px;
  15. border-bottom: 1px solid black;
  16. font-size: 28px;
  17. padding: 4px;
  18. }
  19. #toc {
  20. position: absolute;
  21. top: 40px; left: 0; bottom: 0; width: 200px;
  22. overflow: auto;
  23. padding: 4px;
  24. }
  25. #toc ul {
  26. padding: 0px 15px;
  27. font-size: 80%;
  28. margin: 5px;
  29. }
  30. #toc .sidebar-header-2 {
  31. margin-left: 10px;
  32. list-style-type: circle;
  33. }
  34. #toc .sidebar-header-3 {
  35. margin-left: 20px;
  36. list-style-type: circle;
  37. }
  38. #content {
  39. position: absolute;
  40. top: 40px; right: 0; bottom: 0; left: 200px;
  41. overflow: auto;
  42. border-left: 1px solid black;
  43. padding: 0px 18px;
  44. }
  45. code {
  46. background: #EEE;
  47. border: 1px solid #CCC;
  48. padding: 0 2px;
  49. margin: 0 2px;
  50. }
  51. pre code {
  52. display: block;
  53. margin: 2px;
  54. }
  55. h1 {
  56. font-size: 1.5em;
  57. border-bottom: 1px solid gray;
  58. }
  59. h2 { font-size: 1.2em; }
  60. h3 { font-size: 1.0em; }
  61. </style>
  62. </head>
  63. <body>
  64. <div id="title">C# Promises</div>
  65. <div id="toc"><ul class="nav nav-list">
  66. <li class="sidebar-header-1"><a href="#c#-promises">C# Promises</a></li>
  67. <li class="sidebar-header-1"><a href="#c-sharp-promise">C-Sharp-Promise</a></li>
  68. <li class="sidebar-header-2"><a href="#recent-updates">Recent Updates</a></li>
  69. <li class="sidebar-header-2"><a href="#contents">Contents</a></li>
  70. <li class="sidebar-header-2"><a href="#understanding-promises">Understanding Promises</a></li>
  71. <li class="sidebar-header-2"><a href="#promises/a+-spec">Promises/A+ Spec</a></li>
  72. <li class="sidebar-header-2"><a href="#getting-the-dll">Getting the DLL</a></li>
  73. <li class="sidebar-header-2"><a href="#getting-the-code">Getting the Code</a></li>
  74. <li class="sidebar-header-2"><a href="#creating-a-promise-for-an-async-operation">Creating a Promise for an Async Operation</a></li>
  75. <li class="sidebar-header-2"><a href="#creating-a-promise,-alternate-method">Creating a Promise, Alternate Method</a></li>
  76. <li class="sidebar-header-2"><a href="#waiting-for-an-async-operation-to-complete">Waiting for an Async Operation to Complete</a></li>
  77. <li class="sidebar-header-2"><a href="#chaining-async-operations">Chaining Async Operations</a></li>
  78. <li class="sidebar-header-2"><a href="#transforming-the-results">Transforming the Results</a></li>
  79. <li class="sidebar-header-2"><a href="#error-handling">Error Handling</a></li>
  80. <li class="sidebar-header-2"><a href="#unhandled-errors">Unhandled Errors</a></li>
  81. <li class="sidebar-header-2"><a href="#promises-that-are-already-resolved/rejected">Promises that are already Resolved/Rejected</a></li>
  82. <li class="sidebar-header-2"><a href="#interfaces">Interfaces</a></li>
  83. <li class="sidebar-header-2"><a href="#combining-multiple-async-operations">Combining Multiple Async Operations</a></li>
  84. <li class="sidebar-header-2"><a href="#chaining-multiple-async-operations">Chaining Multiple Async Operations</a></li>
  85. <li class="sidebar-header-2"><a href="#racing-asynchronous-operations">Racing Asynchronous Operations</a></li>
  86. <li class="sidebar-header-2"><a href="#chaining-synchronous-actions-that-have-no-result">Chaining Synchronous Actions that have no Result</a></li>
  87. <li class="sidebar-header-2"><a href="#promises-that-have-no-results-(a-non-value-promise)">Promises that have no Results (a non-value promise)</a></li>
  88. <li class="sidebar-header-2"><a href="#convert-a-value-promise-to-a-non-value-promise">Convert a value promise to a non-value promise</a></li>
  89. <li class="sidebar-header-2"><a href="#running-a-sequence-of-operations">Running a Sequence of Operations</a></li>
  90. <li class="sidebar-header-2"><a href="#combining-parallel-and-sequential-operations">Combining Parallel and Sequential Operations</a></li>
  91. <li class="sidebar-header-2"><a href="#examples">Examples</a></li>
  92. </ul>
  93. </div>
  94. <div id="content"><h1 id="c#-promises"><a class="header-link" href="#c#-promises"></a>C# Promises</h1>
  95. <p>This documents the promise library used in ZFBrowser. This library has been modified, <a href="Readme.html#promises">details</a>.</p>
  96. <p>Original documentation follows:</p>
  97. <h1 id="c-sharp-promise"><a class="header-link" href="#c-sharp-promise"></a>C-Sharp-Promise</h1>
  98. <p><a href="https://promisesaplus.com/">
  99. <img src="https://promisesaplus.com/assets/logo-small.png" alt="Promises/A+ logo"
  100. title="Promises/A+ 1.0 compliant" align="right" />
  101. </a></p>
  102. <p>Promises library for C# for management of asynchronous operations.</p>
  103. <p>Inspired by Javascript promises, but slightly different.</p>
  104. <p>Used by <a href="https://github.com/Real-Serious-Games/C-Sharp-Promise">Real Serious Games</a> for building serious games and simulations on Unity3d.</p>
  105. <p>If you are interested in using promises for game development and Unity please see <a href="http://www.what-could-possibly-go-wrong.com/promises-for-game-development/">this article</a>.</p>
  106. <h2 id="recent-updates"><a class="header-link" href="#recent-updates"></a>Recent Updates</h2>
  107. <ul class="list">
  108. <li>8 March 2015<ul class="list">
  109. <li><em>Transform</em> function has been renamed to <em>Then</em> (another overload of <em>Then</em>).</li>
  110. </ul>
  111. </li>
  112. </ul>
  113. <h2 id="contents"><a class="header-link" href="#contents"></a>Contents</h2>
  114. <!-- START doctoc generated TOC please keep comment here to allow auto update -->
  115. <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
  116. <p><strong>Table of Contents</strong> <em>generated with <a href="https://github.com/thlorenz/doctoc">DocToc</a></em></p>
  117. <ul class="list">
  118. <li><a href="#understanding-promises">Understanding Promises</a></li>
  119. <li><a href="#promisesa-spec">Promises/A+ Spec</a></li>
  120. <li><a href="#getting-the-dll">Getting the DLL</a></li>
  121. <li><a href="#getting-the-code">Getting the Code</a></li>
  122. <li><a href="#creating-a-promise-for-an-async-operation">Creating a Promise for an Async Operation</a></li>
  123. <li><a href="#creating-a-promise-alternate-method">Creating a Promise, Alternate Method</a></li>
  124. <li><a href="#waiting-for-an-async-operation-to-complete">Waiting for an Async Operation to Complete</a></li>
  125. <li><a href="#chaining-async-operations">Chaining Async Operations</a></li>
  126. <li><a href="#transforming-the-results">Transforming the Results</a></li>
  127. <li><a href="#promises-that-are-already-resolvedrejected">Promises that are already Resolved/Rejected</a></li>
  128. <li><a href="#interfaces">Interfaces</a></li>
  129. <li><a href="#combining-multiple-async-operations">Combining Multiple Async Operations</a></li>
  130. <li><a href="#chaining-multiple-async-operations">Chaining Multiple Async Operations</a></li>
  131. <li><a href="#racing-asynchronous-operations">Racing Asynchronous Operations</a></li>
  132. <li><a href="#chaining-synchronous-actions-that-have-no-result">Chaining Synchronous Actions that have no Result</a></li>
  133. <li><a href="#promises-that-have-no-results-a-non-value-promise">Promises that have no Results (a non-value promise)</a></li>
  134. <li><a href="#convert-a-value-promise-to-a-non-value-promise">Convert a value promise to a non-value promise</a></li>
  135. <li><a href="#running-a-sequence-of-operations">Running a Sequence of Operations</a></li>
  136. <li><a href="#combining-parallel-and-sequential-operations">Combining Parallel and Sequential Operations</a></li>
  137. <li><a href="#examples">Examples</a></li>
  138. </ul>
  139. <!-- END doctoc generated TOC please keep comment here to allow auto update -->
  140. <h2 id="understanding-promises"><a class="header-link" href="#understanding-promises"></a>Understanding Promises</h2>
  141. <p>To learn about promises:</p>
  142. <ul class="list">
  143. <li><a href="http://en.wikipedia.org/wiki/Futures_and_promises">Promises on Wikpedia</a></li>
  144. <li><a href="https://www.promisejs.org/">Good overview</a></li>
  145. <li><a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise">Mozilla</a></li>
  146. </ul>
  147. <h2 id="promises/a+-spec"><a class="header-link" href="#promises/a+-spec"></a>Promises/A+ Spec</h2>
  148. <p>This promise library conforms to the <a href="https://promisesaplus.com/">Promises/A+ Spec</a> (at least, as far as is possible with C#):</p>
  149. <h2 id="getting-the-dll"><a class="header-link" href="#getting-the-dll"></a>Getting the DLL</h2>
  150. <p>The DLL can be installed via nuget. Use the Package Manager UI or console in Visual Studio or use nuget from the command line.</p>
  151. <p>See here for instructions on installing a package via nuget: <a href="http://docs.nuget.org/docs/start-here/using-the-package-manager-console">http://docs.nuget.org/docs/start-here/using-the-package-manager-console</a></p>
  152. <p>The package to search for is <em>RSG.Promise</em>.</p>
  153. <h2 id="getting-the-code"><a class="header-link" href="#getting-the-code"></a>Getting the Code</h2>
  154. <p>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:</p>
  155. <pre class="hljs"><code>git <span class="hljs-keyword">clone</span> <span class="hljs-title">https</span>://github.com/Real-Serious-Games/C-Sharp-Promise.git</code></pre><p>Alternately, to contribute please fork the project in github.</p>
  156. <h2 id="creating-a-promise-for-an-async-operation"><a class="header-link" href="#creating-a-promise-for-an-async-operation"></a>Creating a Promise for an Async Operation</h2>
  157. <p>Reference the DLL and import the namespace:</p>
  158. <pre class="hljs"><code>using RSG<span class="hljs-comment">;</span></code></pre><p>Create a promise before you start the async operation:</p>
  159. <pre class="hljs"><code><span class="hljs-keyword">var</span> promise = <span class="hljs-keyword">new</span> Promise&lt;<span class="hljs-keyword">string</span>&gt;();</code></pre><p>The type of the promise should reflect the result of the async op.</p>
  160. <p>Then initiate your async operation and return the promise to the caller.</p>
  161. <p>Upon completion of the async op the promise is resolved:</p>
  162. <pre class="hljs"><code>promise.Resolve(myValue)<span class="hljs-comment">;</span></code></pre><p>The promise is rejected on error/exception:</p>
  163. <pre class="hljs"><code>promise.Reject(myException)<span class="hljs-comment">;</span></code></pre><p>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 <em>unresolved domain name</em>, then the promise is rejected:</p>
  164. <pre class="hljs"><code><span class="hljs-function"><span class="hljs-keyword">public</span> IPromise&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">Download</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> url</span>)
  165. </span>{
  166. <span class="hljs-keyword">var</span> promise = <span class="hljs-keyword">new</span> Promise&lt;<span class="hljs-keyword">string</span>&gt;(); <span class="hljs-comment">// Create promise.</span>
  167. <span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> client = <span class="hljs-keyword">new</span> WebClient())
  168. {
  169. client.DownloadStringCompleted += <span class="hljs-comment">// Monitor event for download completed.</span>
  170. (s, ev) =&gt;
  171. {
  172. <span class="hljs-keyword">if</span> (ev.Error != <span class="hljs-literal">null</span>)
  173. {
  174. promise.Reject(ev.Error); <span class="hljs-comment">// Error during download, reject the promise.</span>
  175. }
  176. <span class="hljs-keyword">else</span>
  177. {
  178. promise.Resolve(ev.Result); <span class="hljs-comment">// Downloaded completed successfully, resolve the promise.</span>
  179. }
  180. };
  181. client.DownloadStringAsync(<span class="hljs-keyword">new</span> Uri(url), <span class="hljs-literal">null</span>); <span class="hljs-comment">// Initiate async op.</span>
  182. }
  183. <span class="hljs-keyword">return</span> promise; <span class="hljs-comment">// Return the promise so the caller can await resolution (or error).</span>
  184. }</code></pre><h2 id="creating-a-promise,-alternate-method"><a class="header-link" href="#creating-a-promise,-alternate-method"></a>Creating a Promise, Alternate Method</h2>
  185. <p>There is another way to create a promise that replicates the JavaScript convention of passing a <em>resolver</em> 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:</p>
  186. <pre class="hljs"><code>var promise = new Promise&lt;string&gt;((<span class="hljs-name">resolve</span>, reject) =&gt;
  187. {
  188. using (<span class="hljs-name">var</span> client = new WebClient())
  189. {
  190. client.DownloadStringCompleted += // Monitor event for download completed.
  191. (<span class="hljs-name">s</span>, ev) =&gt;
  192. {
  193. if (<span class="hljs-name">ev</span>.Error != null)
  194. {
  195. reject(<span class="hljs-name">ev</span>.Error)<span class="hljs-comment">; // Error during download, reject the promise.</span>
  196. }
  197. else
  198. {
  199. resolve(<span class="hljs-name">ev</span>.Result)<span class="hljs-comment">; // Downloaded completed successfully, resolve the promise.</span>
  200. }
  201. }<span class="hljs-comment">;</span>
  202. client.DownloadStringAsync(<span class="hljs-name">new</span> Uri(<span class="hljs-name">url</span>), null)<span class="hljs-comment">; // Initiate async op.</span>
  203. }
  204. })<span class="hljs-comment">;</span></code></pre><h2 id="waiting-for-an-async-operation-to-complete"><a class="header-link" href="#waiting-for-an-async-operation-to-complete"></a>Waiting for an Async Operation to Complete</h2>
  205. <p>The simplest usage is to register a completion handler to be invoked on completion of the async op:</p>
  206. <pre class="hljs"><code>Download(<span class="hljs-string">"http://www.google.com"</span>)
  207. .Done(<span class="hljs-function"><span class="hljs-params">html</span> =&gt;</span>
  208. Console.WriteLine(html)
  209. );</code></pre><p>This snippet downloads the front page from Google and prints it to the console.</p>
  210. <p>For all but the most trivial applications you will also want to register an error hander:</p>
  211. <pre class="hljs"><code>Download(<span class="hljs-string">"http://www.google.com"</span>)
  212. .Catch(<span class="hljs-function"><span class="hljs-params">exception</span> =&gt;</span>
  213. Console.WriteLine(<span class="hljs-string">"An exception occured while downloading!"</span>)
  214. )
  215. .Done(<span class="hljs-function"><span class="hljs-params">html</span> =&gt;</span>
  216. Console.WriteLine(html)
  217. );</code></pre><p>The chain of processing for a promise ends as soon as an error/exception occurs. In this case when an error occurs the <em>Catch</em> handler would be called, but not the <em>Done</em> handler. If there is no error, then only <em>Done</em> is called.</p>
  218. <h2 id="chaining-async-operations"><a class="header-link" href="#chaining-async-operations"></a>Chaining Async Operations</h2>
  219. <p>Multiple async operations can be chained one after the other using <em>Then</em>:</p>
  220. <pre class="hljs"><code>Download(<span class="hljs-string">"http://www.google.com"</span>)
  221. .Then(<span class="hljs-function"><span class="hljs-params">html</span> =&gt;</span>
  222. <span class="hljs-keyword">return</span> Download(ExtractFirstLink(html)) <span class="hljs-comment">// Extract the first link and download it.</span>
  223. )
  224. .Catch(<span class="hljs-function"><span class="hljs-params">exception</span> =&gt;</span>
  225. Console.WriteLine(<span class="hljs-string">"An exception occured while downloading!"</span>)
  226. )
  227. .Done(<span class="hljs-function"><span class="hljs-params">firstLinkHtml</span> =&gt;</span>
  228. Console.WriteLine(firstLinkHtml)
  229. );</code></pre><p>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. <em>Then</em> expects the return value to be another promise. The chained promise can have a different <em>result type</em>.</p>
  230. <h2 id="transforming-the-results"><a class="header-link" href="#transforming-the-results"></a>Transforming the Results</h2>
  231. <p>Sometimes you will want to simply transform or modify the resulting value without chaining another async operation.</p>
  232. <pre class="hljs"><code>Download(<span class="hljs-string">"http://www.google.com"</span>)
  233. <span class="hljs-keyword"> .Then</span>(html =&gt;
  234. <span class="hljs-built_in"> return </span>ExtractAllLinks(html)) // Extract all links<span class="hljs-built_in"> and </span>return an<span class="hljs-built_in"> array </span>of strings.
  235. )
  236. <span class="hljs-keyword"> .Done</span>(links =&gt; // The input here is an<span class="hljs-built_in"> array </span>of strings.
  237. foreach (var link in links)
  238. {
  239. Console.WriteLine(link);
  240. }
  241. );</code></pre><p>As is demonstrated the type of the value can also be changed during transformation. In the previous snippet a <code>Promise&lt;string&gt;</code> is transformed to a <code>Promise&lt;string[]&gt;</code>.</p>
  242. <h2 id="error-handling"><a class="header-link" href="#error-handling"></a>Error Handling</h2>
  243. <p>An error raised in a callback aborts the function and all subsequent callbacks in the chain:</p>
  244. <pre class="hljs"><code>promise.Then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> Something()) <span class="hljs-comment">// &lt;--- An error here aborts all subsequent callbacks...</span>
  245. .Then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> SomethingElse())
  246. .Then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> AnotherThing())
  247. .Catch(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> HandleError(e)) <span class="hljs-comment">// &lt;--- Until the error handler is invoked here.</span></code></pre><h2 id="unhandled-errors"><a class="header-link" href="#unhandled-errors"></a>Unhandled Errors</h2>
  248. <p>When <code>Catch</code> is omitted exceptions go silently unhandled. This is an acknowledged issue with the Promises pattern.</p>
  249. <p>We handle this in a similar way to the JavaScript <a href="http://documentup.com/kriskowal/q">Q</a> library. The <code>Done</code> 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.</p>
  250. <p>Terminating a Promise chain using <code>Done</code>:</p>
  251. <pre class="hljs"><code>promise.Then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> Something())
  252. .Then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> SomethingElse())
  253. .Then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> AnotherThing())
  254. .Done(); <span class="hljs-comment">// &lt;--- Terminate the pipeline and propagate unhandled exceptions.</span></code></pre><p>To use the <code>Done</code> 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 <code>Done</code>.</p>
  255. <p>To hook into the unhandled exception stream:</p>
  256. <pre class="hljs"><code><span class="hljs-attr">Promise.UnhandledException +</span>=<span class="hljs-string"> Promise_UnhandledException;</span></code></pre><p>Then forward the exceptions to your own logging system:</p>
  257. <pre class="hljs"><code><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Promise_UnhandledException</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> sender, ExceptionEventArgs e</span>)
  258. </span>{
  259. Log.Error(e.Exception, <span class="hljs-string">"An unhandled proimses exception occured!"</span>);
  260. }</code></pre><h2 id="promises-that-are-already-resolved/rejected"><a class="header-link" href="#promises-that-are-already-resolved/rejected"></a>Promises that are already Resolved/Rejected</h2>
  261. <p>For convenience or testing you will at some point need to create a promise that <em>starts out</em> in the resolved or rejected state. This is easy to achieve using <em>Resolved</em> and <em>Rejected</em> functions:</p>
  262. <pre class="hljs"><code><span class="hljs-keyword">var</span> resolvedPromise = Promise&lt;<span class="hljs-keyword">string</span>&gt;.Resolved(<span class="hljs-string">"some result"</span>);
  263. <span class="hljs-keyword">var</span> rejectedPromise = Promise&lt;<span class="hljs-keyword">string</span>&gt;.Rejected(someException);</code></pre><h2 id="interfaces"><a class="header-link" href="#interfaces"></a>Interfaces</h2>
  264. <p>The class <em>Promise<T></em> implements the following interfaces:</p>
  265. <ul class="list">
  266. <li><code>IPromise&lt;T&gt;</code> Interface to await promise resolution.</li>
  267. <li><code>IPendingPromise&lt;T&gt;</code> Interface that can resolve or reject the promise.</li>
  268. </ul>
  269. <h2 id="combining-multiple-async-operations"><a class="header-link" href="#combining-multiple-async-operations"></a>Combining Multiple Async Operations</h2>
  270. <p>The <em>All</em> 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.</p>
  271. <p>Say that each promise yields a value of type <em>T</em>, the resulting promise then yields a collection with values of type <em>T</em>.</p>
  272. <p>Here is an example that extracts links from multiple pages and merges the results:</p>
  273. <pre class="hljs"><code><span class="hljs-keyword">var</span> urls = <span class="hljs-keyword">new</span> List&lt;string&gt;();
  274. urls.Add(<span class="hljs-string">"www.google.com"</span>);
  275. urls.Add(<span class="hljs-string">"www.yahoo.com"</span>);
  276. <span class="hljs-built_in">Promise</span>&lt;string[]&gt;
  277. .All(<span class="hljs-function"><span class="hljs-params">url</span> =&gt;</span> Download(url)) <span class="hljs-comment">// Download each URL.</span>
  278. .Then(<span class="hljs-function"><span class="hljs-params">pages</span> =&gt;</span> <span class="hljs-comment">// Receives collection of downloaded pages.</span>
  279. pages.SelectMany(
  280. <span class="hljs-function"><span class="hljs-params">page</span> =&gt;</span> ExtractAllLinks(page) <span class="hljs-comment">// Extract links from all pages then flatten to single collection of links.</span>
  281. )
  282. )
  283. .Done(<span class="hljs-function"><span class="hljs-params">links</span> =&gt;</span> <span class="hljs-comment">// Receives the flattened collection of links from all pages at once.</span>
  284. {
  285. foreach (<span class="hljs-keyword">var</span> link <span class="hljs-keyword">in</span> links)
  286. {
  287. Console.WriteLine(link);
  288. }
  289. });</code></pre><h2 id="chaining-multiple-async-operations"><a class="header-link" href="#chaining-multiple-async-operations"></a>Chaining Multiple Async Operations</h2>
  290. <p>The <em>ThenAll</em> function is a convenient way of chaining multiple promise onto an existing promise:</p>
  291. <pre class="hljs"><code>promise
  292. .Then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> SomeAsyncOperation(result)) <span class="hljs-comment">// Chain a single async operation</span>
  293. .ThenAll(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> <span class="hljs-comment">// Chain multiple async operations.</span>
  294. <span class="hljs-keyword">new</span> IPromise&lt;string&gt;[] <span class="hljs-comment">// Return an enumerable of promises.</span>
  295. {
  296. SomeAsyncOperation1(result),
  297. SomeAsyncOperation2(result),
  298. SomeAsyncOperation3(result)
  299. }
  300. )
  301. .Done(<span class="hljs-function"><span class="hljs-params">collection</span> =&gt;</span> ...); <span class="hljs-comment">// Final promise resolves</span>
  302. <span class="hljs-comment">// with a collection of values</span>
  303. <span class="hljs-comment">// when all operations have completed.</span></code></pre><h2 id="racing-asynchronous-operations"><a class="header-link" href="#racing-asynchronous-operations"></a>Racing Asynchronous Operations</h2>
  304. <p>The <em>Race</em> and <em>ThenRace</em> functions are similar to the <em>All</em> and <em>ThenAll</em> functions, but it is the first async operation that completes that wins the race and it&#39;s value resolves the promise.</p>
  305. <pre class="hljs"><code>promise
  306. .Then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> SomeAsyncOperation(result)) <span class="hljs-comment">// Chain an async operation.</span>
  307. .ThenRace(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> <span class="hljs-comment">// Race multiple async operations.</span>
  308. <span class="hljs-keyword">new</span> IPromise&lt;string&gt;[] <span class="hljs-comment">// Return an enumerable of promises.</span>
  309. {
  310. SomeAsyncOperation1(result),
  311. SomeAsyncOperation2(result),
  312. SomeAsyncOperation3(result)
  313. }
  314. )
  315. .Done(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> ...); <span class="hljs-comment">// The result has come from whichever of</span>
  316. <span class="hljs-comment">// the async operations completed first.</span></code></pre><h2 id="chaining-synchronous-actions-that-have-no-result"><a class="header-link" href="#chaining-synchronous-actions-that-have-no-result"></a>Chaining Synchronous Actions that have no Result</h2>
  317. <p>The <em>Then</em> function can be used to chain synchronous operations that yield no result.</p>
  318. <pre class="hljs"><code><span class="hljs-keyword">var</span> promise = ...
  319. promise
  320. .Then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> SomeAsyncOperation(result)) <span class="hljs-comment">// Chain an async operation.</span>
  321. .Then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> Console.WriteLine(result)) <span class="hljs-comment">// Chain a sync operation that yields no result.</span>
  322. .Done(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> ...); <span class="hljs-comment">// Result from previous ascync operation skips over the *Do* and is passed through.</span></code></pre><h2 id="promises-that-have-no-results-(a-non-value-promise)"><a class="header-link" href="#promises-that-have-no-results-(a-non-value-promise)"></a>Promises that have no Results (a non-value promise)</h2>
  323. <p>What about a promise that has no result? This represents an asynchronous operation that promises only to complete, it doesn&#39;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.</p>
  324. <p><code>Promise</code> is very similar to <code>Promise&lt;T&gt;</code> and implements the similar interfaces: <code>IPromise</code> and <code>IPendingPromise</code>.</p>
  325. <p><code>Promise&lt;T&gt;</code> functions that affect the resulting value have no relevance for the non-value promise and have been removed.</p>
  326. <p>As an example consider the chaining of animation and sound effects as we often need to do in <em>game development</em>:</p>
  327. <pre class="hljs"><code>RunAnimation(<span class="hljs-string">"Foo"</span>) <span class="hljs-regexp">//</span> RunAnimation returns a promise that
  328. .Then(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> RunAnimation(<span class="hljs-string">"Bar"</span>)) <span class="hljs-regexp">//</span> <span class="hljs-keyword">is</span> resolved <span class="hljs-keyword">when</span> the animation <span class="hljs-keyword">is</span> complete.
  329. .Then(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> PlaySound(<span class="hljs-string">"AnimComplete"</span>));</code></pre><h2 id="convert-a-value-promise-to-a-non-value-promise"><a class="header-link" href="#convert-a-value-promise-to-a-non-value-promise"></a>Convert a value promise to a non-value promise</h2>
  330. <p>From time to time you might want to convert a value promise to a non-value promise or vice versa. Both <code>Promise</code> and <code>Promise&lt;T&gt;</code> have overloads of <code>Then</code> and <code>ThenAll</code> that do this conversion. You just need to return the appropriate type of promise (for <code>Then</code>) or enumerable of promises (for <code>ThenAll</code>).</p>
  331. <p>As an example consider a recursive link extractor and file downloader function:</p>
  332. <pre class="hljs"><code><span class="hljs-keyword">public</span> IPromise DownloadAll(<span class="hljs-built_in">string</span> url)
  333. {
  334. <span class="hljs-keyword">return</span> DownloadURL(url) <span class="hljs-comment">// Yields a value, the HTML text downloaded.</span>
  335. .Then(html =&gt; ExtractLinks(html)) <span class="hljs-comment">// Convert HTML into an enumerable of links.</span>
  336. .ThenAll(links =&gt; <span class="hljs-comment">// Process each link.</span>
  337. {
  338. <span class="hljs-comment">// Determine links that should be followed, then follow them.</span>
  339. <span class="hljs-built_in">var</span> linksToFollow = links.<span class="hljs-keyword">Where</span>(<span class="hljs-keyword">link</span> =&gt; IsLinkToFollow(<span class="hljs-keyword">link</span>));
  340. <span class="hljs-built_in">var</span> linksFollowing = linksToFollow.<span class="hljs-keyword">Select</span>(<span class="hljs-keyword">link</span> =&gt; DownloadAll(<span class="hljs-keyword">link</span>));
  341. <span class="hljs-comment">// Determine links that are files to be downloaded, then download them.</span>
  342. <span class="hljs-built_in">var</span> linksToDownload = links.<span class="hljs-keyword">Where</span>(<span class="hljs-keyword">link</span> =&gt; IsLinkToDownload(<span class="hljs-keyword">link</span>));
  343. <span class="hljs-built_in">var</span> linksDownloading = linksToDownload.<span class="hljs-keyword">Select</span>(<span class="hljs-keyword">link</span> =&gt; DownloadFile(<span class="hljs-keyword">link</span>));
  344. <span class="hljs-comment">// Return an enumerable of promises.</span>
  345. <span class="hljs-comment">// This combines the recursive link following and any files we want to download.</span>
  346. <span class="hljs-comment">// Because we are returning an enumerable of non-value promises, the resulting</span>
  347. <span class="hljs-comment">// chained promises is also non-value.</span>
  348. <span class="hljs-keyword">return</span> linksToFollow.Concat(linksDownloading);
  349. });
  350. }</code></pre><p>Usage:</p>
  351. <pre class="hljs"><code>DownloadAll(<span class="hljs-string">"www.somewhere.com"</span>)
  352. .Done(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span>
  353. Console.WriteLine(<span class="hljs-string">"Recursive download completed."</span>);
  354. );</code></pre><h2 id="running-a-sequence-of-operations"><a class="header-link" href="#running-a-sequence-of-operations"></a>Running a Sequence of Operations</h2>
  355. <p>The <code>Sequence</code> and <code>ThenSequence</code> functions build a single promise that wraps multiple sequential operations that will be invoked one after the other.</p>
  356. <p>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.</p>
  357. <pre class="hljs"><code>var sequence = Promise.Sequence(
  358. <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> RunAnimation(<span class="hljs-string">"Foo"</span>),
  359. <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> RunAnimation(<span class="hljs-string">"Bar"</span>),
  360. <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> PlaySound(<span class="hljs-string">"AnimComplete"</span>)
  361. );</code></pre><p>The inputs can also be passed as a collection:</p>
  362. <pre class="hljs"><code><span class="hljs-built_in">var</span> operations = <span class="hljs-params">...</span>
  363. <span class="hljs-built_in">var</span> sequence = Promise.Sequence(operations);</code></pre><p>This might be used, for example, to play a variable length collection of animations based on data:</p>
  364. <pre class="hljs"><code> <span class="hljs-keyword">var</span> animationNames = ... variable length array <span class="hljs-keyword">of</span> animation names loaded <span class="hljs-keyword">from</span> data...
  365. var animations = animationNames.Select(<span class="hljs-function"><span class="hljs-params">animName</span> =&gt;</span> (Func&lt;IPromise&gt;)(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> RunAnimation(animName)));
  366. <span class="hljs-keyword">var</span> sequence = <span class="hljs-built_in">Promise</span>.Sequence(animations);
  367. sequence
  368. .Done(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span>
  369. {
  370. <span class="hljs-comment">// All animations have completed in sequence.</span>
  371. });</code></pre><p>Unfortunately we find that we have reached the limits of what is possible with C# type inference, hence the use of the ugly cast <code>(Func&lt;IPromise&gt;)</code>.</p>
  372. <p>The cast can easily be removed by converting the inner anonymous function to an actual function which I&#39;ll call <code>PrepAnimation</code>:</p>
  373. <pre class="hljs"><code>private Func&lt;IPromise&gt; PrepAnimation(string animName)
  374. {
  375. <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> RunAnimation(animName);
  376. }
  377. <span class="hljs-keyword">var</span> animations = animationNames.Select(<span class="hljs-function"><span class="hljs-params">animName</span> =&gt;</span> PrepAnimation(animName));
  378. <span class="hljs-keyword">var</span> sequence = <span class="hljs-built_in">Promise</span>.Sequence(animations);
  379. sequence
  380. .Done(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span>
  381. {
  382. <span class="hljs-comment">// All animations have completed in sequence.</span>
  383. });</code></pre><p>Holy cow... we&#39;ve just careened into <a href="http://en.wikipedia.org/wiki/Functional_programming">functional programming</a> territory, herein lies very powerful and expressive programming techniques.</p>
  384. <h2 id="combining-parallel-and-sequential-operations"><a class="header-link" href="#combining-parallel-and-sequential-operations"></a>Combining Parallel and Sequential Operations</h2>
  385. <p>We can easily combine sequential and parallel operations to build very expressive logic.</p>
  386. <pre class="hljs"><code>Promise.Sequence( // Play operations <span class="hljs-number">1</span> <span class="hljs-keyword">and</span> <span class="hljs-number">2</span> sequently.
  387. () =&gt; Promise.All( // Operation <span class="hljs-number">1</span>: Play <span class="hljs-built_in">animation</span> <span class="hljs-keyword">and</span> sound <span class="hljs-built_in">at</span> same <span class="hljs-built_in">time</span>.
  388. RunAnimation(<span class="hljs-string">"Foo"</span>),
  389. PlaySound(<span class="hljs-string">"Bar"</span>)
  390. ),
  391. () =&gt; Promise.All(
  392. RunAnimation(<span class="hljs-string">"One"</span>), // Operation <span class="hljs-number">2</span>: Play <span class="hljs-built_in">animation</span> <span class="hljs-keyword">and</span> sound <span class="hljs-built_in">at</span> same <span class="hljs-built_in">time</span>.
  393. PlaySound(<span class="hljs-string">"Two"</span>)
  394. )
  395. );</code></pre><p>I&#39;m starting to feel like we are defining behavior trees.</p>
  396. <h2 id="examples"><a class="header-link" href="#examples"></a>Examples</h2>
  397. <ul class="list">
  398. <li>Example1<ul class="list">
  399. <li>Example of downloading text from a URL using a promise.</li>
  400. </ul>
  401. </li>
  402. <li>Example2<ul class="list">
  403. <li>Example of a promise that is rejected because of an error during</li>
  404. <li>the async operation.</li>
  405. </ul>
  406. </li>
  407. <li>Example3<ul class="list">
  408. <li>This example downloads search results from google then transforms the result to extract links.</li>
  409. <li>Includes both error handling and a completion handler.</li>
  410. </ul>
  411. </li>
  412. <li>Example4<ul class="list">
  413. <li>This example downloads search results from google, extracts the links and follows only a single first link, downloads its then prints the result.</li>
  414. <li>Includes both error handling and a completion handler.</li>
  415. </ul>
  416. </li>
  417. <li>Example5<ul class="list">
  418. <li>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.</li>
  419. </ul>
  420. </li>
  421. </ul>
  422. </div>
  423. </body></html>