2025-01-12 04:36:52 +08:00

775 lines
45 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Using promises with Shiny</title>
<script>// Pandoc 2.9 adds attributes on both header and div. We remove the former (to
// be compatible with the behavior of Pandoc < 2.8).
document.addEventListener('DOMContentLoaded', function(e) {
var hs = document.querySelectorAll("div.section[class*='level'] > :first-child");
var i, h, a;
for (i = 0; i < hs.length; i++) {
h = hs[i];
if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6
a = h.attributes;
while (a.length > 0) h.removeAttribute(a[0].name);
}
});
</script>
<style type="text/css">
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
</style>
<style type="text/css">
code {
white-space: pre;
}
.sourceCode {
overflow: visible;
}
</style>
<style type="text/css" data-origin="pandoc">
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; }
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.at { color: #7d9029; }
code span.bn { color: #40a070; }
code span.bu { color: #008000; }
code span.cf { color: #007020; font-weight: bold; }
code span.ch { color: #4070a0; }
code span.cn { color: #880000; }
code span.co { color: #60a0b0; font-style: italic; }
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.do { color: #ba2121; font-style: italic; }
code span.dt { color: #902000; }
code span.dv { color: #40a070; }
code span.er { color: #ff0000; font-weight: bold; }
code span.ex { }
code span.fl { color: #40a070; }
code span.fu { color: #06287e; }
code span.im { color: #008000; font-weight: bold; }
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.kw { color: #007020; font-weight: bold; }
code span.op { color: #666666; }
code span.ot { color: #007020; }
code span.pp { color: #bc7a00; }
code span.sc { color: #4070a0; }
code span.ss { color: #bb6688; }
code span.st { color: #4070a0; }
code span.va { color: #19177c; }
code span.vs { color: #4070a0; }
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; }
</style>
<script>
// apply pandoc div.sourceCode style to pre.sourceCode instead
(function() {
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].ownerNode.dataset["origin"] !== "pandoc") continue;
try { var rules = sheets[i].cssRules; } catch (e) { continue; }
var j = 0;
while (j < rules.length) {
var rule = rules[j];
// check if there is a div.sourceCode rule
if (rule.type !== rule.STYLE_RULE || rule.selectorText !== "div.sourceCode") {
j++;
continue;
}
var style = rule.style.cssText;
// check if color or background-color is set
if (rule.style.color === '' && rule.style.backgroundColor === '') {
j++;
continue;
}
// replace div.sourceCode by a pre.sourceCode rule
sheets[i].deleteRule(j);
sheets[i].insertRule('pre.sourceCode{' + style + '}', j);
}
}
})();
</script>
<style type="text/css">body {
background-color: #fff;
margin: 1em auto;
max-width: 700px;
overflow: visible;
padding-left: 2em;
padding-right: 2em;
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.35;
}
#TOC {
clear: both;
margin: 0 0 10px 10px;
padding: 4px;
width: 400px;
border: 1px solid #CCCCCC;
border-radius: 5px;
background-color: #f6f6f6;
font-size: 13px;
line-height: 1.3;
}
#TOC .toctitle {
font-weight: bold;
font-size: 15px;
margin-left: 5px;
}
#TOC ul {
padding-left: 40px;
margin-left: -1.5em;
margin-top: 5px;
margin-bottom: 5px;
}
#TOC ul ul {
margin-left: -2em;
}
#TOC li {
line-height: 16px;
}
table {
margin: 1em auto;
border-width: 1px;
border-color: #DDDDDD;
border-style: outset;
border-collapse: collapse;
}
table th {
border-width: 2px;
padding: 5px;
border-style: inset;
}
table td {
border-width: 1px;
border-style: inset;
line-height: 18px;
padding: 5px 5px;
}
table, table th, table td {
border-left-style: none;
border-right-style: none;
}
table thead, table tr.even {
background-color: #f7f7f7;
}
p {
margin: 0.5em 0;
}
blockquote {
background-color: #f6f6f6;
padding: 0.25em 0.75em;
}
hr {
border-style: solid;
border: none;
border-top: 1px solid #777;
margin: 28px 0;
}
dl {
margin-left: 0;
}
dl dd {
margin-bottom: 13px;
margin-left: 13px;
}
dl dt {
font-weight: bold;
}
ul {
margin-top: 0;
}
ul li {
list-style: circle outside;
}
ul ul {
margin-bottom: 0;
}
pre, code {
background-color: #f7f7f7;
border-radius: 3px;
color: #333;
white-space: pre-wrap;
}
pre {
border-radius: 3px;
margin: 5px 0px 10px 0px;
padding: 10px;
}
pre:not([class]) {
background-color: #f7f7f7;
}
code {
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 85%;
}
p > code, li > code {
padding: 2px 0px;
}
div.figure {
text-align: center;
}
img {
background-color: #FFFFFF;
padding: 2px;
border: 1px solid #DDDDDD;
border-radius: 3px;
border: 1px solid #CCCCCC;
margin: 0 5px;
}
h1 {
margin-top: 0;
font-size: 35px;
line-height: 40px;
}
h2 {
border-bottom: 4px solid #f7f7f7;
padding-top: 10px;
padding-bottom: 2px;
font-size: 145%;
}
h3 {
border-bottom: 2px solid #f7f7f7;
padding-top: 10px;
font-size: 120%;
}
h4 {
border-bottom: 1px solid #f7f7f7;
margin-left: 8px;
font-size: 105%;
}
h5, h6 {
border-bottom: 1px solid #ccc;
font-size: 105%;
}
a {
color: #0033dd;
text-decoration: none;
}
a:hover {
color: #6666ff; }
a:visited {
color: #800080; }
a:visited:hover {
color: #BB00BB; }
a[href^="http:"] {
text-decoration: underline; }
a[href^="https:"] {
text-decoration: underline; }
code > span.kw { color: #555; font-weight: bold; }
code > span.dt { color: #902000; }
code > span.dv { color: #40a070; }
code > span.bn { color: #d14; }
code > span.fl { color: #d14; }
code > span.ch { color: #d14; }
code > span.st { color: #d14; }
code > span.co { color: #888888; font-style: italic; }
code > span.ot { color: #007020; }
code > span.al { color: #ff0000; font-weight: bold; }
code > span.fu { color: #900; font-weight: bold; }
code > span.er { color: #a61717; background-color: #e3d2d2; }
</style>
</head>
<body>
<h1 class="title toc-ignore">Using promises with Shiny</h1>
<style>
.alert-success a {
color: white;
text-decoration: underline;
}
</style>
<div class="alert alert-success">
<p>As of Shiny 1.8.1, there is a new feature called <strong>Extended
Tasks</strong> that has several advantages over the approach in this
article. We highly recommend that you read <a href="https://shiny.posit.co/r/articles/improve/nonblocking/">the
Extended Task documentation</a> first.</p>
</div>
<p>Taking advantage of async programming from Shiny is not as simple as
turning on an option or flipping a switch. If you have already written a
Shiny application and are looking to improve its scalability, expect the
changes required for async operation to ripple through multiple layers
of server code.</p>
<p>Async programming with Shiny boils down to following a few steps.</p>
<ol style="list-style-type: decimal">
<li><p>Identify slow operations (function calls or blocks of statements)
in your app.</p></li>
<li><p>Convert the slow operation into a future using
<code>future_promise()</code>. (If you havent read the <a href="promises_04_futures.html">article on futures</a> and <a href="promises_05_future_promise.html"><code>future_promise()</code></a>,
definitely do that before proceeding!)</p></li>
<li><p>Any code that relies on the result of that operation (if any),
whether directly or indirectly, now must be converted to promise
handlers that operate on the future object.</p></li>
</ol>
<p>Well get into details for all these steps, but first, an example.
Consider the following synchronous server code:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="cf">function</span>(input, output, session) {</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> output<span class="sc">$</span>plot <span class="ot">&lt;-</span> <span class="fu">renderPlot</span>({</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> result <span class="ot">&lt;-</span> <span class="fu">expensive_operation</span>()</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> result <span class="ot">&lt;-</span> <span class="fu">head</span>(result, input<span class="sc">$</span>n)</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">plot</span>(result)</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>Wed convert it to async like this:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="fu">library</span>(promises)</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="fu">library</span>(future)</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="fu">plan</span>(multisession)</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="cf">function</span>(input, output, session) {</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a> output<span class="sc">$</span>plot <span class="ot">&lt;-</span> <span class="fu">renderPlot</span>({</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({ <span class="fu">expensive_operation</span>() }) <span class="sc">%...&gt;%</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> <span class="fu">head</span>(input<span class="sc">$</span>n) <span class="sc">%...&gt;%</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> <span class="fu">plot</span>()</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<div id="adding-prerequisites" class="section level2">
<h2>Adding prerequisites</h2>
<p>The easiest part is adding <code>library(promises)</code>,
<code>library(future)</code>, and <code>plan(multisession)</code> to the
top of the app.</p>
<p>The <code>promises</code> library is necessary for the
<code>%...&gt;%</code> operator. You may also want to use promise
utility functions like <code>promise_all</code> and
<code>promise_race</code>.</p>
<p>The <code>future</code> library is needed because the
<code>future()</code> function call used inside
<code>future_promise()</code> is how you will launch asynchronous
tasks.</p>
<p><code>plan(multisession)</code> is a directive to the
<code>future</code> package, telling it how future tasks should actually
be executed. See the <a href="promises_04_futures.html">article on
futures</a> for more details.</p>
</div>
<div id="identifying-slow-operations" class="section level2">
<h2>Identifying slow operations</h2>
<p>To find areas of your code that are good candidates for the
future/promise treatment, lets start with the obvious: identifying the
code that is making your app slow. You may assume its your plotting
code thats slow, but its actually your database queries; or vice
versa. If theres one thing that veteran programmers can agree on, its
that human intuition is a surprisingly unreliable tool for spotting
performance problems.</p>
<p>Our recommendation is that you use the <a href="https://profvis.r-lib.org/">profvis</a> profiler, which we
designed to work with Shiny (see Example 3 in the profvis
documentation). You can use profvis to help you focus in on where the
time is actually being spent in your app.</p>
<blockquote>
<p><strong>Note:</strong> As of this writing, profvis doesnt work
particularly well for diagnosing performance problems in parts of your
code that youve already made asynchronous. In particular, we havent
done any work to help it profile code that executes in a future, and the
mechanism we use to hide “irrelevant” parts of the stack trace doesnt
work well with promises. These are ripe areas for future
development.</p>
</blockquote>
<p>Async programming works well when you can identify just a few
“hotspots” in your app where lots of time is being spent. It works much
less well if your app is too slow because of a generalized, diffuse
slowness through every aspect of your app, where no one operation takes
too much time but it all adds up to a lot. The more futures you need to
introduce into your app, the more fixed communication overhead you
incur. So for the most bang-for-the-buck, we want to launch a small
number of futures per session but move a lot of the waited-on code into
each one.</p>
</div>
<div id="converting-a-slow-operation-into-a-future" class="section level2">
<h2>Converting a slow operation into a future</h2>
<p>Now that weve found hotspots that we want to make asynchronous,
lets talk about the actual work of converting them to futures.</p>
<p>Conceptually, futures work like this:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="fu">future</span>({</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a> <span class="co"># Expensive code goes here</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>}) <span class="sc">%...&gt;%</span> (<span class="cf">function</span>(result) {</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> <span class="co"># Code to handle result of expensive code goes here</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>})</span></code></pre></div>
<p>which seems incredibly simple. Whats actually happening is that the
future runs in a totally separate child R process, and then the result
is collected up and returned to the main R process:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Code here runs in process A</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="fu">future</span>({</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> <span class="co"># Code here runs in (child) process B</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>}) <span class="sc">%...&gt;%</span> (<span class="cf">function</span>(result) {</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a> <span class="co"># Code here runs in process A</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>})</span></code></pre></div>
<p>The fact that the future code block executes in a separate process
means we have to take special care to deal with a number of practical
issues. There are extremely important constraints that futures impose on
their code blocks; certain objects cannot be safely used across process
boundaries, and some of the default behaviors of the future library may
severely impact the performance of your app. Again, see the <a href="promises_04_futures.html">article on futures</a> for more
details.</p>
<p>The remainder of this document will use <code>future_promise()</code>
in place of <code>future()</code>. For more information on the
differences, see the <a href="promises_05_future_promise.html">article
on the benefits of <code>future_promise()</code></a>.</p>
<div id="shiny-specific-caveats-and-limitations" class="section level3">
<h3>Shiny-specific caveats and limitations</h3>
<p>In addition to the constraints that all futures face, there is an
additional one for Shiny: reactive values and reactive expressions
cannot be read from within a future. Whenever reactive
values/expressions are read, side effects are carried out under the hood
so that the currently executing observer or reactive expression can be
notified when the reactive value/expression becomes invalidated. If a
reactive value/expression is created in one process, but read in another
process, there will be no way for readers to be notified about
invalidation.</p>
<p>This code, for example, will not work:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="cf">function</span>(input, output, session) {</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a> r1 <span class="ot">&lt;-</span> <span class="fu">reactive</span>({ ... })</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a> r2 <span class="ot">&lt;-</span> <span class="fu">reactive</span>({</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a> <span class="fu">r1</span>() <span class="co"># Will error--don&#39;t do this!</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>Even though <code>r1()</code> is called from inside the
<code>r2</code> reactive expression, the fact that its also in a future
means the call will fail. Instead, you must read any reactive
values/expressions you need in advance of launching the future:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="cf">function</span>(input, output, session) {</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a> r1 <span class="ot">&lt;-</span> <span class="fu">reactive</span>({ ... })</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a> r2 <span class="ot">&lt;-</span> <span class="fu">reactive</span>({</span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a> val <span class="ot">&lt;-</span> <span class="fu">r1</span>()</span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({</span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a> val <span class="co"># No problem!</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>However, its perfectly fine to read reactive values/expressions from
inside a promise <em>handler</em>. Handlers run in the original process,
not a child process, so reactive operations are allowed.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="cf">function</span>(input, output, session) {</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a> r1 <span class="ot">&lt;-</span> <span class="fu">reactive</span>({ ... })</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> r2 <span class="ot">&lt;-</span> <span class="fu">reactive</span>({</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({ ... }) <span class="sc">%...&gt;%</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> <span class="fu">rbind</span>(<span class="fu">r1</span>()) <span class="co"># OK!</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
</div>
</div>
<div id="integrating-promises-with-shiny" class="section level2">
<h2>Integrating promises with Shiny</h2>
<p>Generally, youll be using promises with Shiny from within outputs,
reactive expressions, and observers. Weve tried to integrate promises
into these constructs in as natural a way as possible.</p>
<div id="outputs" class="section level3">
<h3>Outputs</h3>
<p>Most outputs (<code>renderXXX({ ... })</code>) functions expect your
code block to return a value; for example, <code>renderText()</code>
expects a character vector and <code>renderTable()</code> expects a data
frame. All such render functions that are included within the
<code>shiny</code> package can now optionally be given a promise for
such a value instead.</p>
<p>So this:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a>output<span class="sc">$</span>table <span class="ot">&lt;-</span> <span class="fu">renderTable</span>({</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a> <span class="fu">read.csv</span>(url) <span class="sc">%&gt;%</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">filter</span>(date <span class="sc">==</span> input<span class="sc">$</span>date)</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a>})</span></code></pre></div>
<p>could become:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a>output<span class="sc">$</span>table <span class="ot">&lt;-</span> <span class="fu">renderTable</span>({</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({ <span class="fu">read.csv</span>(url) }) <span class="sc">%...&gt;%</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">filter</span>(date <span class="sc">==</span> input<span class="sc">$</span>date)</span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a>})</span></code></pre></div>
<p>or, trading elegance for efficiency:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a>output<span class="sc">$</span>table <span class="ot">&lt;-</span> <span class="fu">renderTable</span>({</span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a> input_date <span class="ot">&lt;-</span> input<span class="sc">$</span>date</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({</span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a> <span class="fu">read.csv</span>(url) <span class="sc">%&gt;%</span> <span class="fu">filter</span>(date <span class="sc">==</span> input_date)</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a>})</span></code></pre></div>
<p>The important thing to keep in mind is that the promise (or promise
pipeline) must be the final expression in the code block. Shiny only
knows about promises you actually return to it when you hand control
back.</p>
<div id="render-functions-with-side-effects-renderprint-and-renderplot" class="section level4">
<h4>Render functions with side effects: <code>renderPrint</code> and
<code>renderPlot</code></h4>
<p>The render functions <code>renderPrint()</code> and
<code>renderPlot()</code> are slightly different than other render
functions, in that they can be affected by side effects in the code
block you provide. In <code>renderPrint</code> you can print to the
console, and in <code>renderPlot</code> you can plot to the active R
graphics device.</p>
<p>With promises, these render functions can work in a similar way, but
with a caveat. As you hopefully understand by now, futures execute their
code in a separate R process, and printing/plotting in a separate
process wont have any effect on the Shiny output in the original
process. These examples, then, are incorrect:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a>output<span class="sc">$</span>summary <span class="ot">&lt;-</span> <span class="fu">renderPrint</span>({</span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({</span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">read.csv</span>(url) <span class="sc">%&gt;%</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a> <span class="fu">summary</span>() <span class="sc">%&gt;%</span></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">print</span>()</span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a>})</span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a>output<span class="sc">$</span>plot <span class="ot">&lt;-</span> <span class="fu">renderPlot</span>({</span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({</span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a> df <span class="ot">&lt;-</span> <span class="fu">read.csv</span>(url)</span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a> <span class="fu">ggplot</span>(df, <span class="fu">aes</span>(length, width)) <span class="sc">+</span> <span class="fu">geom_point</span>()</span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a>})</span></code></pre></div>
<p>Instead, do printing and plotting after control returns back to the
original process, via a promise handler:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a>output<span class="sc">$</span>summary <span class="ot">&lt;-</span> <span class="fu">renderPrint</span>({</span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({ <span class="fu">read.csv</span>(url) }) <span class="sc">%...&gt;%</span></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">summary</span>() <span class="sc">%...&gt;%</span></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a> <span class="fu">print</span>()</span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a>})</span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a>output<span class="sc">$</span>plot <span class="ot">&lt;-</span> <span class="fu">renderPlot</span>({</span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({ <span class="fu">read.csv</span>(url) }) <span class="sc">%...&gt;%</span></span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a> {</span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a> <span class="fu">ggplot</span>(., <span class="fu">aes</span>(length, width)) <span class="sc">+</span> <span class="fu">geom_point</span>()</span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a>})</span></code></pre></div>
<p>Again, you do need to be careful to make sure that the last
expression in your code block is the promise/pipeline; this is the only
way the rendering logic can know whether and when your logic has
completed, and if any errors occurred (so they can be displayed to the
user).</p>
</div>
</div>
<div id="observers" class="section level3">
<h3>Observers</h3>
<p>Observers are very similar to outputs: you must make sure that the
last expression in your code block is the promise/pipeline. Like
outputs, observers need to know whether and when theyre done running,
and if any errors occured (so they can log them and terminate the user
session). The way to communicate this from your async user code is by
returning the promise.</p>
<p>Heres a synchronous example that well convert to async. Clicking
the <code>refresh_data</code> action button causes data to be
downloaded, which is then saved to disk as <code>cached.rds</code> and
also used to update the reactive value <code>data</code>.</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a>data <span class="ot">&lt;-</span> <span class="fu">reactiveVal</span>(<span class="fu">readRDS</span>(<span class="st">&quot;cached.rds&quot;</span>))</span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a><span class="cf">function</span>(input, output, session) {</span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a> <span class="fu">observeEvent</span>(input<span class="sc">$</span>refresh_data, {</span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a> df <span class="ot">&lt;-</span> <span class="fu">read.csv</span>(url)</span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a> <span class="fu">saveRDS</span>(df, <span class="st">&quot;cached.rds&quot;</span>)</span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">data</span>(df)</span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>And the async version:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a>data <span class="ot">&lt;-</span> <span class="fu">reactiveVal</span>(<span class="fu">readRDS</span>(<span class="st">&quot;cached.rds&quot;</span>))</span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a><span class="cf">function</span>(input, output, session) {</span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a> <span class="fu">observeEvent</span>(input<span class="sc">$</span>refresh_data, {</span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({</span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a> df <span class="ot">&lt;-</span> <span class="fu">read.csv</span>(url)</span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">saveRDS</span>(df, <span class="st">&quot;cached.rds&quot;</span>)</span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a> df</span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true" tabindex="-1"></a> }) <span class="sc">%...&gt;%</span></span>
<span id="cb14-10"><a href="#cb14-10" aria-hidden="true" tabindex="-1"></a> <span class="fu">data</span>()</span>
<span id="cb14-11"><a href="#cb14-11" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb14-12"><a href="#cb14-12" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>Note that in this version, we cannot call <code>data(df)</code>
inside the future, as this would cause the update to happen in the wrong
process. Instead, we use the <code>%...&gt;%</code> operator to perform
the assignment back in the main process once the future resolves.</p>
</div>
<div id="reactive-expressions" class="section level3">
<h3>Reactive expressions</h3>
<p>Recall that reactive expressions are used to calculate values, and
are cached until they are automatically invalidated by one of their
dependencies. Unlike outputs and observers, reactive expressions can be
used from other reactive consumers.</p>
<p>Asynchronous reactive expressions are similar to regular
(synchronous) reactive expressions: instead of a “normal” value, they
return a promise that will yield the desired value; and a normal
reactive will cache a normal value, while an async reactive will cache
the promise.</p>
<p>The upshot is that when defining an async reactive expression, your
code block should return a promise or promise pipeline, following the
same rules as reactive outputs. And when calling an async reactive
expression, call it like a function like you would a regular reactive
expression, and treat the value thats returned like any other
promise.</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="cf">function</span>(input, output, session) {</span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a> data <span class="ot">&lt;-</span> <span class="fu">eventReactive</span>(input<span class="sc">$</span>refresh_data, {</span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">read.csv</span>(url)</span>
<span id="cb15-4"><a href="#cb15-4" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb15-5"><a href="#cb15-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb15-6"><a href="#cb15-6" aria-hidden="true" tabindex="-1"></a> filteredData <span class="ot">&lt;-</span> <span class="fu">reactive</span>({</span>
<span id="cb15-7"><a href="#cb15-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">data</span>() <span class="sc">%&gt;%</span> <span class="fu">filter</span>(date <span class="sc">==</span> input<span class="sc">$</span>date)</span>
<span id="cb15-8"><a href="#cb15-8" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb15-9"><a href="#cb15-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb15-10"><a href="#cb15-10" aria-hidden="true" tabindex="-1"></a> output<span class="sc">$</span>table <span class="ot">&lt;-</span> <span class="fu">renderTable</span>({</span>
<span id="cb15-11"><a href="#cb15-11" aria-hidden="true" tabindex="-1"></a> <span class="fu">filteredData</span>() <span class="sc">%&gt;%</span> <span class="fu">head</span>(<span class="dv">5</span>)</span>
<span id="cb15-12"><a href="#cb15-12" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb15-13"><a href="#cb15-13" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>And now in async:</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="cf">function</span>(input, output, session) {</span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a> data <span class="ot">&lt;-</span> <span class="fu">eventReactive</span>(input<span class="sc">$</span>refresh_data, {</span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">future_promise</span>({ <span class="fu">read.csv</span>(url) })</span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true" tabindex="-1"></a> filteredData <span class="ot">&lt;-</span> <span class="fu">reactive</span>({</span>
<span id="cb16-7"><a href="#cb16-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">data</span>() <span class="sc">%...&gt;%</span> <span class="fu">filter</span>(date <span class="sc">==</span> input<span class="sc">$</span>date)</span>
<span id="cb16-8"><a href="#cb16-8" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb16-9"><a href="#cb16-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb16-10"><a href="#cb16-10" aria-hidden="true" tabindex="-1"></a> output<span class="sc">$</span>table <span class="ot">&lt;-</span> <span class="fu">renderTable</span>({</span>
<span id="cb16-11"><a href="#cb16-11" aria-hidden="true" tabindex="-1"></a> <span class="fu">filteredData</span>() <span class="sc">%...&gt;%</span> <span class="fu">head</span>(<span class="dv">5</span>)</span>
<span id="cb16-12"><a href="#cb16-12" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb16-13"><a href="#cb16-13" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
</div>
</div>
<div id="the-flush-cycle" class="section level2">
<h2>The flush cycle</h2>
<p>In the past, Shinys reactive programming model has operated using a
mostly traditional <a href="https://en.wikipedia.org/wiki/Event_loop">event loop</a> model.
Somewhere many levels beneath <code>shiny::runApp()</code> was a piece
of code that looked a bit like this:</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="cf">while</span> (<span class="cn">TRUE</span>) {</span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a> <span class="co"># Do nothing until a browser sends some data</span></span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a> input <span class="ot">&lt;-</span> <span class="fu">receiveInputFromBrowser</span>()</span>
<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a> <span class="co"># Use the received data to update reactive inputs</span></span>
<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a> session<span class="sc">$</span><span class="fu">updateInput</span>(input)</span>
<span id="cb17-6"><a href="#cb17-6" aria-hidden="true" tabindex="-1"></a> <span class="co"># Execute all invalidated outputs/observers</span></span>
<span id="cb17-7"><a href="#cb17-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">flushReact</span>()</span>
<span id="cb17-8"><a href="#cb17-8" aria-hidden="true" tabindex="-1"></a> <span class="co"># After ALL outputs execute, send the results back</span></span>
<span id="cb17-9"><a href="#cb17-9" aria-hidden="true" tabindex="-1"></a> <span class="fu">flushOutputs</span>()</span>
<span id="cb17-10"><a href="#cb17-10" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>We call this Shinys “flush cycle”. There are two important
properties to our flush cycle.</p>
<ol style="list-style-type: decimal">
<li>Only one of the four steps—receiving, updating, reacting, and
sending—can be executing a time. (Remember, R is single threaded.) In
particular, its not possible for inputs to be updated while
outputs/observers are running. This is important in order to avoid race
conditions that would be all but impossible to defend against.</li>
<li>Many outputs may change as a result of a single input value received
from the browser, but none of them are sent back to the client until all
of the outputs are ready. The advantage of this is a smoother experience
for the end-user in most cases. (Admittedly, there has been some
controversy regarding this property of Shiny; some app authors would
strongly prefer to show outputs as soon as they are ready, or at least
to have manual control over this behavior.)</li>
</ol>
<p>While adding async support to Shiny, we aimed to keep these two
properties intact. Imagine now that <code>flushReact()</code>, the line
that executes invalidated outputs/observers, returns a promise that
combines all of the async outputs/observers (i.e. a promise that
resolves only after all of the async outputs/observers have resolved).
The new, async-aware event loop is conceptually more like this:</p>
<div class="sourceCode" id="cb18"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a>doEventLoop <span class="ot">&lt;-</span> <span class="cf">function</span>() {</span>
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a> <span class="co"># Do nothing until a browser sends some data</span></span>
<span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a> input <span class="ot">&lt;-</span> <span class="fu">receiveInputFromBrowser</span>()</span>
<span id="cb18-4"><a href="#cb18-4" aria-hidden="true" tabindex="-1"></a> <span class="co"># Use the received data to update reactive inputs</span></span>
<span id="cb18-5"><a href="#cb18-5" aria-hidden="true" tabindex="-1"></a> session<span class="sc">$</span><span class="fu">updateInput</span>(input)</span>
<span id="cb18-6"><a href="#cb18-6" aria-hidden="true" tabindex="-1"></a> <span class="co"># Execute all invalidated outputs/observers</span></span>
<span id="cb18-7"><a href="#cb18-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">flushReact</span>() <span class="sc">%...&gt;%</span> {</span>
<span id="cb18-8"><a href="#cb18-8" aria-hidden="true" tabindex="-1"></a> <span class="co"># After ALL outputs execute, send the results back</span></span>
<span id="cb18-9"><a href="#cb18-9" aria-hidden="true" tabindex="-1"></a> <span class="fu">flushOutputs</span>()</span>
<span id="cb18-10"><a href="#cb18-10" aria-hidden="true" tabindex="-1"></a> <span class="co"># Continue the event loop</span></span>
<span id="cb18-11"><a href="#cb18-11" aria-hidden="true" tabindex="-1"></a> <span class="fu">doEventLoop</span>()</span>
<span id="cb18-12"><a href="#cb18-12" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb18-13"><a href="#cb18-13" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>The resulting behavior matches the synchronous version of the event
loop, in that:</p>
<ol style="list-style-type: decimal">
<li>No inputs are received from the browser until all pending async
outputs/observers have completed. Unlike the synchronous version, this
separation is enforced at the session level: if Session A has some async
observers that have not finished executing, that only prevents Session A
from processing new input values, while new input values from Session B
can be handled immediately because they belong to a different session.
Again, the goal of keeping input updates separate from output/observer
execution is to prevent race conditions, which are even more pernicious
to debug and understand when async code is involved.</li>
<li>For a given session, no outputs are sent back to the client, until
all outputs are ready. It doesnt matter whether the outputs in question
are synchronous, asynchronous, or some combination; they all must
complete execution before any can be sent.</li>
</ol>
</div>
<!-- code folding -->
<!-- dynamically load mathjax for compatibility with self-contained -->
<script>
(function () {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
document.getElementsByTagName("head")[0].appendChild(script);
})();
</script>
</body>
</html>