单机版网页启动程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

450 lines
38 KiB

<html>
<head>
<meta charset="UTF-8">
<title>C# Promises</title>
<link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABL1BMVEUAAAAAAAAAAAAAAAAAAAAQAAABBBwZAAAAFAUUAAAADwMAAAAAAAAAAAAAAAAAAAAAAAAABgIuAAAsAAAANQwAGAUAAg0AAAAAAAAAAAAAAAAAAAAAwzIAvzAEOvwEPP8ESP0EQ/0EP/0ABQvtAAADMfgAHpkA1DkAzjcAyzYAyDUAuC0AaBj4AADyAADpAADmAADjAADWAADLAABAAAAFS/8DNP8FTf4DNvwEOPYDK/ICIroCEF0A0DkAvC8AnyYAnCUAkCIAdxwAAhgAQQ8bBAECBAB4AQD7AADwAADfAADcAAC5AAC0AACIAABOAAADMf8DJcwCIbACHaMCGYsBD0EBDTYAlSEAjSEASREASBEAAw8TAgwAMQsCJQkALwgAQAT0AADhAABjAABeAABD9A36AAAAHHRSTlMAGK5/eP7B1syvpoloYFUTAfnr6ujkvaaUb10gmPaQKAAAAOZJREFUGNM1j9dWwlAQRSeFSBGw13tBTQA1HSLSpPdmV3r//29gILDfzj7rrDUDGxif2+l0+xjYcgT8WbGQyxWUc942l9XqpCfLWUrLHkA4q9NsWwqlhJCyF/cnpqrWpUqekOen7DEDN11N0+b1WhHzY2TAg6stSY2GWlMwv0X6LBzEdF2XjMqYYqbiBQrTMGJWqTTLy//TbwFcneYiOkonk+k/8fczxYK32zLfwy939w/BePzjhwfmtBV1BF9RBBKJlOjHw5Z7EQhlDgG5Wu0moYzH/u7aIX5hHR5ib3PLsYLAcn5A1tqnJNnWRf14AAAAAElFTkSuQmCC">
<style type="text/css">
body {
font-family: sans-serif;
margin: 0;
padding: 0;
}
* { box-sizing: border-box; }
#title {
position: absolute;
top: 0; left: 0; right: 0; height: 40px;
border-bottom: 1px solid black;
font-size: 28px;
padding: 4px;
}
#toc {
position: absolute;
top: 40px; left: 0; bottom: 0; width: 200px;
overflow: auto;
padding: 4px;
}
#toc ul {
padding: 0px 15px;
font-size: 80%;
margin: 5px;
}
#toc .sidebar-header-2 {
margin-left: 10px;
list-style-type: circle;
}
#toc .sidebar-header-3 {
margin-left: 20px;
list-style-type: circle;
}
#content {
position: absolute;
top: 40px; right: 0; bottom: 0; left: 200px;
overflow: auto;
border-left: 1px solid black;
padding: 0px 18px;
}
code {
background: #EEE;
border: 1px solid #CCC;
padding: 0 2px;
margin: 0 2px;
}
pre code {
display: block;
margin: 2px;
}
h1 {
font-size: 1.5em;
border-bottom: 1px solid gray;
}
h2 { font-size: 1.2em; }
h3 { font-size: 1.0em; }
</style>
</head>
<body>
<div id="title">C# Promises</div>
<div id="toc"><ul class="nav nav-list">
<li class="sidebar-header-1"><a href="#c#-promises">C# Promises</a></li>
<li class="sidebar-header-1"><a href="#c-sharp-promise">C-Sharp-Promise</a></li>
<li class="sidebar-header-2"><a href="#recent-updates">Recent Updates</a></li>
<li class="sidebar-header-2"><a href="#contents">Contents</a></li>
<li class="sidebar-header-2"><a href="#understanding-promises">Understanding Promises</a></li>
<li class="sidebar-header-2"><a href="#promises/a+-spec">Promises/A+ Spec</a></li>
<li class="sidebar-header-2"><a href="#getting-the-dll">Getting the DLL</a></li>
<li class="sidebar-header-2"><a href="#getting-the-code">Getting the Code</a></li>
<li class="sidebar-header-2"><a href="#creating-a-promise-for-an-async-operation">Creating a Promise for an Async Operation</a></li>
<li class="sidebar-header-2"><a href="#creating-a-promise,-alternate-method">Creating a Promise, Alternate Method</a></li>
<li class="sidebar-header-2"><a href="#waiting-for-an-async-operation-to-complete">Waiting for an Async Operation to Complete</a></li>
<li class="sidebar-header-2"><a href="#chaining-async-operations">Chaining Async Operations</a></li>
<li class="sidebar-header-2"><a href="#transforming-the-results">Transforming the Results</a></li>
<li class="sidebar-header-2"><a href="#error-handling">Error Handling</a></li>
<li class="sidebar-header-2"><a href="#unhandled-errors">Unhandled Errors</a></li>
<li class="sidebar-header-2"><a href="#promises-that-are-already-resolved/rejected">Promises that are already Resolved/Rejected</a></li>
<li class="sidebar-header-2"><a href="#interfaces">Interfaces</a></li>
<li class="sidebar-header-2"><a href="#combining-multiple-async-operations">Combining Multiple Async Operations</a></li>
<li class="sidebar-header-2"><a href="#chaining-multiple-async-operations">Chaining Multiple Async Operations</a></li>
<li class="sidebar-header-2"><a href="#racing-asynchronous-operations">Racing Asynchronous Operations</a></li>
<li class="sidebar-header-2"><a href="#chaining-synchronous-actions-that-have-no-result">Chaining Synchronous Actions that have no Result</a></li>
<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>
<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>
<li class="sidebar-header-2"><a href="#running-a-sequence-of-operations">Running a Sequence of Operations</a></li>
<li class="sidebar-header-2"><a href="#combining-parallel-and-sequential-operations">Combining Parallel and Sequential Operations</a></li>
<li class="sidebar-header-2"><a href="#examples">Examples</a></li>
</ul>
</div>
<div id="content"><h1 id="c#-promises"><a class="header-link" href="#c#-promises"></a>C# Promises</h1>
<p>This documents the promise library used in ZFBrowser. This library has been modified, <a href="Readme.html#promises">details</a>.</p>
<p>Original documentation follows:</p>
<h1 id="c-sharp-promise"><a class="header-link" href="#c-sharp-promise"></a>C-Sharp-Promise</h1>
<p><a href="https://promisesaplus.com/">
<img src="https://promisesaplus.com/assets/logo-small.png" alt="Promises/A+ logo"
title="Promises/A+ 1.0 compliant" align="right" />
</a></p>
<p>Promises library for C# for management of asynchronous operations.</p>
<p>Inspired by Javascript promises, but slightly different.</p>
<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>
<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>
<h2 id="recent-updates"><a class="header-link" href="#recent-updates"></a>Recent Updates</h2>
<ul class="list">
<li>8 March 2015<ul class="list">
<li><em>Transform</em> function has been renamed to <em>Then</em> (another overload of <em>Then</em>).</li>
</ul>
</li>
</ul>
<h2 id="contents"><a class="header-link" href="#contents"></a>Contents</h2>
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
<p><strong>Table of Contents</strong> <em>generated with <a href="https://github.com/thlorenz/doctoc">DocToc</a></em></p>
<ul class="list">
<li><a href="#understanding-promises">Understanding Promises</a></li>
<li><a href="#promisesa-spec">Promises/A+ Spec</a></li>
<li><a href="#getting-the-dll">Getting the DLL</a></li>
<li><a href="#getting-the-code">Getting the Code</a></li>
<li><a href="#creating-a-promise-for-an-async-operation">Creating a Promise for an Async Operation</a></li>
<li><a href="#creating-a-promise-alternate-method">Creating a Promise, Alternate Method</a></li>
<li><a href="#waiting-for-an-async-operation-to-complete">Waiting for an Async Operation to Complete</a></li>
<li><a href="#chaining-async-operations">Chaining Async Operations</a></li>
<li><a href="#transforming-the-results">Transforming the Results</a></li>
<li><a href="#promises-that-are-already-resolvedrejected">Promises that are already Resolved/Rejected</a></li>
<li><a href="#interfaces">Interfaces</a></li>
<li><a href="#combining-multiple-async-operations">Combining Multiple Async Operations</a></li>
<li><a href="#chaining-multiple-async-operations">Chaining Multiple Async Operations</a></li>
<li><a href="#racing-asynchronous-operations">Racing Asynchronous Operations</a></li>
<li><a href="#chaining-synchronous-actions-that-have-no-result">Chaining Synchronous Actions that have no Result</a></li>
<li><a href="#promises-that-have-no-results-a-non-value-promise">Promises that have no Results (a non-value promise)</a></li>
<li><a href="#convert-a-value-promise-to-a-non-value-promise">Convert a value promise to a non-value promise</a></li>
<li><a href="#running-a-sequence-of-operations">Running a Sequence of Operations</a></li>
<li><a href="#combining-parallel-and-sequential-operations">Combining Parallel and Sequential Operations</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<h2 id="understanding-promises"><a class="header-link" href="#understanding-promises"></a>Understanding Promises</h2>
<p>To learn about promises:</p>
<ul class="list">
<li><a href="http://en.wikipedia.org/wiki/Futures_and_promises">Promises on Wikpedia</a></li>
<li><a href="https://www.promisejs.org/">Good overview</a></li>
<li><a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise">Mozilla</a></li>
</ul>
<h2 id="promises/a+-spec"><a class="header-link" href="#promises/a+-spec"></a>Promises/A+ Spec</h2>
<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>
<h2 id="getting-the-dll"><a class="header-link" href="#getting-the-dll"></a>Getting the DLL</h2>
<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>
<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>
<p>The package to search for is <em>RSG.Promise</em>.</p>
<h2 id="getting-the-code"><a class="header-link" href="#getting-the-code"></a>Getting the Code</h2>
<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>
<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>
<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>
<p>Reference the DLL and import the namespace:</p>
<pre class="hljs"><code>using RSG<span class="hljs-comment">;</span></code></pre><p>Create a promise before you start the async operation:</p>
<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>
<p>Then initiate your async operation and return the promise to the caller.</p>
<p>Upon completion of the async op the promise is resolved:</p>
<pre class="hljs"><code>promise.Resolve(myValue)<span class="hljs-comment">;</span></code></pre><p>The promise is rejected on error/exception:</p>
<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>
<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>)
</span>{
<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>
<span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> client = <span class="hljs-keyword">new</span> WebClient())
{
client.DownloadStringCompleted += <span class="hljs-comment">// Monitor event for download completed.</span>
(s, ev) =&gt;
{
<span class="hljs-keyword">if</span> (ev.Error != <span class="hljs-literal">null</span>)
{
promise.Reject(ev.Error); <span class="hljs-comment">// Error during download, reject the promise.</span>
}
<span class="hljs-keyword">else</span>
{
promise.Resolve(ev.Result); <span class="hljs-comment">// Downloaded completed successfully, resolve the promise.</span>
}
};
client.DownloadStringAsync(<span class="hljs-keyword">new</span> Uri(url), <span class="hljs-literal">null</span>); <span class="hljs-comment">// Initiate async op.</span>
}
<span class="hljs-keyword">return</span> promise; <span class="hljs-comment">// Return the promise so the caller can await resolution (or error).</span>
}</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>
<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>
<pre class="hljs"><code>var promise = new Promise&lt;string&gt;((<span class="hljs-name">resolve</span>, reject) =&gt;
{
using (<span class="hljs-name">var</span> client = new WebClient())
{
client.DownloadStringCompleted += // Monitor event for download completed.
(<span class="hljs-name">s</span>, ev) =&gt;
{
if (<span class="hljs-name">ev</span>.Error != null)
{
reject(<span class="hljs-name">ev</span>.Error)<span class="hljs-comment">; // Error during download, reject the promise.</span>
}
else
{
resolve(<span class="hljs-name">ev</span>.Result)<span class="hljs-comment">; // Downloaded completed successfully, resolve the promise.</span>
}
}<span class="hljs-comment">;</span>
client.DownloadStringAsync(<span class="hljs-name">new</span> Uri(<span class="hljs-name">url</span>), null)<span class="hljs-comment">; // Initiate async op.</span>
}
})<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>
<p>The simplest usage is to register a completion handler to be invoked on completion of the async op:</p>
<pre class="hljs"><code>Download(<span class="hljs-string">"http://www.google.com"</span>)
.Done(<span class="hljs-function"><span class="hljs-params">html</span> =&gt;</span>
Console.WriteLine(html)
);</code></pre><p>This snippet downloads the front page from Google and prints it to the console.</p>
<p>For all but the most trivial applications you will also want to register an error hander:</p>
<pre class="hljs"><code>Download(<span class="hljs-string">"http://www.google.com"</span>)
.Catch(<span class="hljs-function"><span class="hljs-params">exception</span> =&gt;</span>
Console.WriteLine(<span class="hljs-string">"An exception occured while downloading!"</span>)
)
.Done(<span class="hljs-function"><span class="hljs-params">html</span> =&gt;</span>
Console.WriteLine(html)
);</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>
<h2 id="chaining-async-operations"><a class="header-link" href="#chaining-async-operations"></a>Chaining Async Operations</h2>
<p>Multiple async operations can be chained one after the other using <em>Then</em>:</p>
<pre class="hljs"><code>Download(<span class="hljs-string">"http://www.google.com"</span>)
.Then(<span class="hljs-function"><span class="hljs-params">html</span> =&gt;</span>
<span class="hljs-keyword">return</span> Download(ExtractFirstLink(html)) <span class="hljs-comment">// Extract the first link and download it.</span>
)
.Catch(<span class="hljs-function"><span class="hljs-params">exception</span> =&gt;</span>
Console.WriteLine(<span class="hljs-string">"An exception occured while downloading!"</span>)
)
.Done(<span class="hljs-function"><span class="hljs-params">firstLinkHtml</span> =&gt;</span>
Console.WriteLine(firstLinkHtml)
);</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>
<h2 id="transforming-the-results"><a class="header-link" href="#transforming-the-results"></a>Transforming the Results</h2>
<p>Sometimes you will want to simply transform or modify the resulting value without chaining another async operation.</p>
<pre class="hljs"><code>Download(<span class="hljs-string">"http://www.google.com"</span>)
<span class="hljs-keyword"> .Then</span>(html =&gt;
<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.
)
<span class="hljs-keyword"> .Done</span>(links =&gt; // The input here is an<span class="hljs-built_in"> array </span>of strings.
foreach (var link in links)
{
Console.WriteLine(link);
}
);</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>
<h2 id="error-handling"><a class="header-link" href="#error-handling"></a>Error Handling</h2>
<p>An error raised in a callback aborts the function and all subsequent callbacks in the chain:</p>
<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>
.Then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> SomethingElse())
.Then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> AnotherThing())
.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>
<p>When <code>Catch</code> is omitted exceptions go silently unhandled. This is an acknowledged issue with the Promises pattern.</p>
<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>
<p>Terminating a Promise chain using <code>Done</code>:</p>
<pre class="hljs"><code>promise.Then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> Something())
.Then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> SomethingElse())
.Then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> AnotherThing())
.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>
<p>To hook into the unhandled exception stream:</p>
<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>
<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>)
</span>{
Log.Error(e.Exception, <span class="hljs-string">"An unhandled proimses exception occured!"</span>);
}</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>
<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>
<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>);
<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>
<p>The class <em>Promise<T></em> implements the following interfaces:</p>
<ul class="list">
<li><code>IPromise&lt;T&gt;</code> Interface to await promise resolution.</li>
<li><code>IPendingPromise&lt;T&gt;</code> Interface that can resolve or reject the promise.</li>
</ul>
<h2 id="combining-multiple-async-operations"><a class="header-link" href="#combining-multiple-async-operations"></a>Combining Multiple Async Operations</h2>
<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>
<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>
<p>Here is an example that extracts links from multiple pages and merges the results:</p>
<pre class="hljs"><code><span class="hljs-keyword">var</span> urls = <span class="hljs-keyword">new</span> List&lt;string&gt;();
urls.Add(<span class="hljs-string">"www.google.com"</span>);
urls.Add(<span class="hljs-string">"www.yahoo.com"</span>);
<span class="hljs-built_in">Promise</span>&lt;string[]&gt;
.All(<span class="hljs-function"><span class="hljs-params">url</span> =&gt;</span> Download(url)) <span class="hljs-comment">// Download each URL.</span>
.Then(<span class="hljs-function"><span class="hljs-params">pages</span> =&gt;</span> <span class="hljs-comment">// Receives collection of downloaded pages.</span>
pages.SelectMany(
<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>
)
)
.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>
{
foreach (<span class="hljs-keyword">var</span> link <span class="hljs-keyword">in</span> links)
{
Console.WriteLine(link);
}
});</code></pre><h2 id="chaining-multiple-async-operations"><a class="header-link" href="#chaining-multiple-async-operations"></a>Chaining Multiple Async Operations</h2>
<p>The <em>ThenAll</em> function is a convenient way of chaining multiple promise onto an existing promise:</p>
<pre class="hljs"><code>promise
.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>
.ThenAll(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> <span class="hljs-comment">// Chain multiple async operations.</span>
<span class="hljs-keyword">new</span> IPromise&lt;string&gt;[] <span class="hljs-comment">// Return an enumerable of promises.</span>
{
SomeAsyncOperation1(result),
SomeAsyncOperation2(result),
SomeAsyncOperation3(result)
}
)
.Done(<span class="hljs-function"><span class="hljs-params">collection</span> =&gt;</span> ...); <span class="hljs-comment">// Final promise resolves</span>
<span class="hljs-comment">// with a collection of values</span>
<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>
<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>
<pre class="hljs"><code>promise
.Then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> SomeAsyncOperation(result)) <span class="hljs-comment">// Chain an async operation.</span>
.ThenRace(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> <span class="hljs-comment">// Race multiple async operations.</span>
<span class="hljs-keyword">new</span> IPromise&lt;string&gt;[] <span class="hljs-comment">// Return an enumerable of promises.</span>
{
SomeAsyncOperation1(result),
SomeAsyncOperation2(result),
SomeAsyncOperation3(result)
}
)
.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>
<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>
<p>The <em>Then</em> function can be used to chain synchronous operations that yield no result.</p>
<pre class="hljs"><code><span class="hljs-keyword">var</span> promise = ...
promise
.Then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> SomeAsyncOperation(result)) <span class="hljs-comment">// Chain an async operation.</span>
.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>
.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>
<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>
<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>
<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>
<p>As an example consider the chaining of animation and sound effects as we often need to do in <em>game development</em>:</p>
<pre class="hljs"><code>RunAnimation(<span class="hljs-string">"Foo"</span>) <span class="hljs-regexp">//</span> RunAnimation returns a promise that
.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.
.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>
<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>
<p>As an example consider a recursive link extractor and file downloader function:</p>
<pre class="hljs"><code><span class="hljs-keyword">public</span> IPromise DownloadAll(<span class="hljs-built_in">string</span> url)
{
<span class="hljs-keyword">return</span> DownloadURL(url) <span class="hljs-comment">// Yields a value, the HTML text downloaded.</span>
.Then(html =&gt; ExtractLinks(html)) <span class="hljs-comment">// Convert HTML into an enumerable of links.</span>
.ThenAll(links =&gt; <span class="hljs-comment">// Process each link.</span>
{
<span class="hljs-comment">// Determine links that should be followed, then follow them.</span>
<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>));
<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>));
<span class="hljs-comment">// Determine links that are files to be downloaded, then download them.</span>
<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>));
<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>));
<span class="hljs-comment">// Return an enumerable of promises.</span>
<span class="hljs-comment">// This combines the recursive link following and any files we want to download.</span>
<span class="hljs-comment">// Because we are returning an enumerable of non-value promises, the resulting</span>
<span class="hljs-comment">// chained promises is also non-value.</span>
<span class="hljs-keyword">return</span> linksToFollow.Concat(linksDownloading);
});
}</code></pre><p>Usage:</p>
<pre class="hljs"><code>DownloadAll(<span class="hljs-string">"www.somewhere.com"</span>)
.Done(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span>
Console.WriteLine(<span class="hljs-string">"Recursive download completed."</span>);
);</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>
<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>
<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>
<pre class="hljs"><code>var sequence = Promise.Sequence(
<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> RunAnimation(<span class="hljs-string">"Foo"</span>),
<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> RunAnimation(<span class="hljs-string">"Bar"</span>),
<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> PlaySound(<span class="hljs-string">"AnimComplete"</span>)
);</code></pre><p>The inputs can also be passed as a collection:</p>
<pre class="hljs"><code><span class="hljs-built_in">var</span> operations = <span class="hljs-params">...</span>
<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>
<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...
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)));
<span class="hljs-keyword">var</span> sequence = <span class="hljs-built_in">Promise</span>.Sequence(animations);
sequence
.Done(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span>
{
<span class="hljs-comment">// All animations have completed in sequence.</span>
});</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>
<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>
<pre class="hljs"><code>private Func&lt;IPromise&gt; PrepAnimation(string animName)
{
<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> RunAnimation(animName);
}
<span class="hljs-keyword">var</span> animations = animationNames.Select(<span class="hljs-function"><span class="hljs-params">animName</span> =&gt;</span> PrepAnimation(animName));
<span class="hljs-keyword">var</span> sequence = <span class="hljs-built_in">Promise</span>.Sequence(animations);
sequence
.Done(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span>
{
<span class="hljs-comment">// All animations have completed in sequence.</span>
});</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>
<h2 id="combining-parallel-and-sequential-operations"><a class="header-link" href="#combining-parallel-and-sequential-operations"></a>Combining Parallel and Sequential Operations</h2>
<p>We can easily combine sequential and parallel operations to build very expressive logic.</p>
<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.
() =&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>.
RunAnimation(<span class="hljs-string">"Foo"</span>),
PlaySound(<span class="hljs-string">"Bar"</span>)
),
() =&gt; Promise.All(
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>.
PlaySound(<span class="hljs-string">"Two"</span>)
)
);</code></pre><p>I&#39;m starting to feel like we are defining behavior trees.</p>
<h2 id="examples"><a class="header-link" href="#examples"></a>Examples</h2>
<ul class="list">
<li>Example1<ul class="list">
<li>Example of downloading text from a URL using a promise.</li>
</ul>
</li>
<li>Example2<ul class="list">
<li>Example of a promise that is rejected because of an error during</li>
<li>the async operation.</li>
</ul>
</li>
<li>Example3<ul class="list">
<li>This example downloads search results from google then transforms the result to extract links.</li>
<li>Includes both error handling and a completion handler.</li>
</ul>
</li>
<li>Example4<ul class="list">
<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>
<li>Includes both error handling and a completion handler.</li>
</ul>
</li>
<li>Example5<ul class="list">
<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>
</ul>
</li>
</ul>
</div>
</body></html>