{"componentChunkName":"component---src-templates-blog-post-js","path":"/solve-fresh-of-loading/","result":{"data":{"site":{"siteMetadata":{"title":"😏","author":"thoamsy"}},"markdownRemark":{"id":"a02b466d-e8a2-5ef3-99d8-b3776c0a6773","html":"<h2 id=\"背景\" style=\"position:relative;\"><a href=\"#%E8%83%8C%E6%99%AF\" aria-label=\"背景 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>背景</h2>\n<p>从开始接触异步请求和 Promise 后，就会发现这些操作很<strong>「套路化」</strong>。绝大多数的异步操作似乎都可以类似下面 👇 的代码：</p>\n<div class=\"gatsby-highlight\" data-language=\"jsx\"><pre class=\"language-jsx\"><code class=\"language-jsx\"><span class=\"token keyword\">async</span> <span class=\"token function\">componentDidMount</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">setState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> loading<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> rep <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">fetch</span><span class=\"token punctuation\">(</span>params<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">setState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> <span class=\"token operator\">...</span>rep<span class=\"token punctuation\">.</span>data <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span> <span class=\"token keyword\">finally</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">setState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> loading<span class=\"token operator\">:</span> <span class=\"token boolean\">false</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>这种套路话的方式，和 <code class=\"language-text\">await</code> 的引入，确实让我们在处理异步问题的时候简单了很多。但是这也带来了一些体验上的缺陷。在请求返回足够快（9102 年了，网速不是问题）的情况下，会给 loading 带来一种转瞬即逝的效果 🌚。</p>\n<p>如果 loading UI 侵入性比较小的话，可能不是那么让人讨厌，但想象一个全屏幕的 loading：它唰地出现了一下子又消失了。你这个时候就不会觉得这是很好的用户体验了，反而觉得很蠢。</p>\n<p>所以总结两点，不分青红皂白就 loading，在高速网络下带来的问题</p>\n<ol>\n<li>频繁出现 loading，会让用户觉得变卡了</li>\n<li>闪烁的体验让人很分裂</li>\n</ol>\n<h2 id=\"promise-的解决方案\" style=\"position:relative;\"><a href=\"#promise-%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88\" aria-label=\"promise 的解决方案 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Promise 的解决方案</h2>\n<p>你可以不用读完，直接看<a href=\"https://codesandbox.io/s/kxzxnnj8vr\">完成代码</a>就行了</p>\n<p>主要的实现思路就是通过在<strong>请求和超时</strong>中竞争出优先被 <code class=\"language-text\">settled</code> 的那个 <code class=\"language-text\">Promise</code>，作出相应的逻辑。\n为了实现的简单，超时的函数可以写成</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token function-variable function\">timeout</span> <span class=\"token operator\">=</span> <span class=\"token parameter\">ms</span> <span class=\"token operator\">=></span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Promise</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">_<span class=\"token punctuation\">,</span> reject</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token function\">setTimeout</span><span class=\"token punctuation\">(</span>reject<span class=\"token punctuation\">,</span> ms<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p><code class=\"language-text\">timeout</code> 的实现使用 <code class=\"language-text\">reject</code> 而不是 <code class=\"language-text\">resolve</code> 可以让代码更加简洁。因为进入 <em>fulfilled</em> 的情况只有一种，就是在 ms 之内请服务器响应并返回了。而在 <code class=\"language-text\">rejected</code> 状态下，就是超时的逻辑（<strong>注意 ⚠️，这里不考虑请求异常的情况</strong>）</p>\n<p>根据上面的说法，很容易写出一个 <em>naive</em> 的实现</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> rep <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> Promise<span class=\"token punctuation\">.</span><span class=\"token function\">race</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token function\">timeout</span><span class=\"token punctuation\">(</span><span class=\"token number\">400</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token function\">fetch</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">setState</span><span class=\"token punctuation\">(</span>rep<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">setState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> loading<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>但是这不符合我们的要求。如果没有进入 <code class=\"language-text\">catch</code> 里，世界和平，但是如果进入 <code class=\"language-text\">catch</code> 里，谁帮我们继续处理返回的数据呢？换句话说，它只把一个 loading 丢给你，其他就不管了！</p>\n<p>接着，我们改进一下：</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> rep <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> Promise<span class=\"token punctuation\">.</span><span class=\"token function\">race</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token function\">timeout</span><span class=\"token punctuation\">(</span><span class=\"token number\">400</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token function\">fetch</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">setState</span><span class=\"token punctuation\">(</span>rep<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">setState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> loading<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> rep <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">fetch</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">setState</span><span class=\"token punctuation\">(</span>rep<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>这种方式和上面的差别是在 <code class=\"language-text\">catch</code> 中做了显示 loading 之后的所需操作的。遗憾的是，它通过继续调用一次 <code class=\"language-text\">fetch</code> 来想当然的认为能处理问题。但其实，我们只需要将 <code class=\"language-text\">fetch(url)</code> 的结果保存下来，就可以做到了。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">let</span> fetchPromise <span class=\"token operator\">=</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n  fetchPromise <span class=\"token operator\">=</span> <span class=\"token function\">fetch</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> rep <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> Promise<span class=\"token punctuation\">.</span><span class=\"token function\">race</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token function\">timeout</span><span class=\"token punctuation\">(</span><span class=\"token number\">400</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> fetchPromise<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">setState</span><span class=\"token punctuation\">(</span>rep<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">setState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> loading<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> rep <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> fetchPromise<span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">setState</span><span class=\"token punctuation\">(</span>rep<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>It’s Done 😎.</p>\n<h2 id=\"让它更加通用\" style=\"position:relative;\"><a href=\"#%E8%AE%A9%E5%AE%83%E6%9B%B4%E5%8A%A0%E9%80%9A%E7%94%A8\" aria-label=\"让它更加通用 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>让它更加通用</h2>\n<p>接下来的任务就是让这个场景更加通用，可以通过一个<strong>高阶函数</strong>，把普通的异步函数作为参数传入，并返回一个支持该特性的函数。</p>\n<p>我们先写一个最符合直觉的函数，它需要哪些参数：</p>\n<ol>\n<li>异步函数</li>\n<li>超时时间</li>\n<li>接口返回后的回调</li>\n<li>超时的回调</li>\n</ol>\n<p>下面使用 Promise 而不是 <code class=\"language-text\">async</code> 来实现，因为这样会让代码更简单。实现如下：</p>\n<div class=\"gatsby-highlight\" data-language=\"jsx\"><pre class=\"language-jsx\"><code class=\"language-jsx\"><span class=\"token keyword\">const</span> <span class=\"token function-variable function\">timeout</span> <span class=\"token operator\">=</span> <span class=\"token parameter\">ms</span> <span class=\"token operator\">=></span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Promise</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">_<span class=\"token punctuation\">,</span> r</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token function\">setTimeout</span><span class=\"token punctuation\">(</span>r<span class=\"token punctuation\">,</span> ms<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token function-variable function\">rq</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">api<span class=\"token punctuation\">,</span> ms<span class=\"token punctuation\">,</span> resolve<span class=\"token punctuation\">,</span> reject</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\"><span class=\"token operator\">...</span>args</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> request <span class=\"token operator\">=</span> <span class=\"token function\">api</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>args<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  Promise<span class=\"token punctuation\">.</span><span class=\"token function\">race</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span>request<span class=\"token punctuation\">,</span> <span class=\"token function\">timeout</span><span class=\"token punctuation\">(</span>ms<span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span>resolve<span class=\"token punctuation\">,</span> <span class=\"token parameter\">err</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">reject</span><span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> request<span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span>resolve<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>这样已经能拿到我们需要的效果了，但是一个函数介绍四个参数的话，未免有点多，让人觉得不太自然。\n通过观察第 5 行和第 7 行，我们发现 <code class=\"language-text\">request</code> 后下一个 fulfilled 回调是 <code class=\"language-text\">resolve</code>，<code class=\"language-text\">timeout</code> 在解决完 <code class=\"language-text\">reject</code> 后下一个 <code class=\"language-text\">fulfilled</code> 回调也是 <code class=\"language-text\">resolve</code>。也就是说，其实我们可以将代码改成这样 <code class=\"language-text\">ajax(api, ms, reject).then(resolve)</code>。</p>\n<p>看下改进后的代码：</p>\n<div class=\"gatsby-highlight\" data-language=\"jsx\"><pre class=\"language-jsx\"><code class=\"language-jsx\"><span class=\"token keyword\">const</span> <span class=\"token function-variable function\">timeout</span> <span class=\"token operator\">=</span> <span class=\"token parameter\">ms</span> <span class=\"token operator\">=></span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Promise</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">_<span class=\"token punctuation\">,</span> r</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token function\">setTimeout</span><span class=\"token punctuation\">(</span>r<span class=\"token punctuation\">,</span> ms<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token function-variable function\">rq</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">api<span class=\"token punctuation\">,</span> ms<span class=\"token punctuation\">,</span> reject</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">(</span><span class=\"token parameter\"><span class=\"token operator\">...</span>args</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> request <span class=\"token operator\">=</span> <span class=\"token function\">api</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>args<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> Promise<span class=\"token punctuation\">.</span><span class=\"token function\">race</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span>request<span class=\"token punctuation\">,</span> <span class=\"token function\">timeout</span><span class=\"token punctuation\">(</span>ms<span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">undefined</span><span class=\"token punctuation\">,</span> <span class=\"token parameter\">err</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">reject</span><span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> request<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>这里代码和 👆 的区别主要是第五行中的 then，不再是 resolve，而是 undefined。为什么呢？\n看下 MDN 中，关于 <code class=\"language-text\">Promise.then</code> 的一个特别的定义。</p>\n<blockquote>\n<p>If one or both arguments are omitted or are provided non-functions, then then will be missing the handler(s), but will not generate any errors. If the Promise that then is called on adopts a state (fulfillment or rejection) for which then has no handler, a new Promise is created with no additional handlers, simply adopting the final state of the original Promise on which then was called.</p>\n</blockquote>\n<p>翻译过来其实就是，如果 then 里的两个参数有一个函数没有声明的话，就会直接返回一个新的 Promise，并抛出去。虽然看上去很难理解，其实，我们常用的 <code class=\"language-text\">catch</code> 不就是 <code class=\"language-text\">then(undefined, reject)</code> 的语法糖？最后可以改成</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">const rq = (api, ms, reject) =&gt; (...args) =&gt; {\n  const request = api(...args);\n  return Promise.race([request, timeout(ms)]).catch(err =&gt; {\n    reject(err);\n    return request;\n  });\n};</code></pre></div>\n<h2 id=\"未解决问题\" style=\"position:relative;\"><a href=\"#%E6%9C%AA%E8%A7%A3%E5%86%B3%E9%97%AE%E9%A2%98\" aria-label=\"未解决问题 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>未解决问题</h2>\n<p>在整个实现中，我们忽略了一个问题，那就是 request 也有 <strong>throw 异常</strong> 的情况，一个关键的问题就需要在 catch 中区分到底是超时还是服务器异常导致的问题。而为了区分这个，最好的方式就是 Symbol。</p>\n<p>因为它是<strong>唯一</strong>的。下面为了代码的清晰，我改成了 <code class=\"language-text\">Symbol.for</code>。\n可以将 <code class=\"language-text\">timeout</code> 改为：</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token function-variable function\">timeout</span> <span class=\"token operator\">=</span> <span class=\"token parameter\">ms</span> <span class=\"token operator\">=></span>\n  <span class=\"token keyword\">new</span> <span class=\"token class-name\">Promise</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">_<span class=\"token punctuation\">,</span> reject</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n    <span class=\"token function\">setTimeout</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token function\">reject</span><span class=\"token punctuation\">(</span>Symbol<span class=\"token punctuation\">.</span><span class=\"token function\">for</span><span class=\"token punctuation\">(</span><span class=\"token string\">'timeout'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> ms<span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>接着我们在 catch 的时候做一个判断，将服务端异常的造成的问题抛出去</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">catch</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">err</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>Symbol<span class=\"token punctuation\">.</span><span class=\"token function\">for</span><span class=\"token punctuation\">(</span><span class=\"token string\">'timeout'</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">===</span> err<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">reject</span><span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> request<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">throw</span> err<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<h3 id=\"使用实例\" style=\"position:relative;\"><a href=\"#%E4%BD%BF%E7%94%A8%E5%AE%9E%E4%BE%8B\" aria-label=\"使用实例 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>使用实例</h3>\n<div class=\"gatsby-highlight\" data-language=\"jsx\"><pre class=\"language-jsx\"><code class=\"language-jsx\"><span class=\"token keyword\">const</span> <span class=\"token function-variable function\">Table</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\"><span class=\"token punctuation\">{</span>fetch<span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">[</span>loading<span class=\"token punctuation\">,</span> setLoading<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token function\">useState</span><span class=\"token punctuation\">(</span><span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">[</span>list<span class=\"token punctuation\">,</span> setList<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token function\">useState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token keyword\">const</span> powerfulFetch <span class=\"token operator\">=</span> <span class=\"token function\">useCallback</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token function\">rq</span><span class=\"token punctuation\">(</span>fetch<span class=\"token punctuation\">,</span> <span class=\"token number\">500</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token function\">setLoading</span><span class=\"token punctuation\">(</span><span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">[</span>fetch<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token function\">useEffect</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">powerfulFetch</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n      <span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">res</span> <span class=\"token operator\">=></span> <span class=\"token function\">setList</span><span class=\"token punctuation\">(</span>res<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">.</span><span class=\"token function\">finally</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token function\">setLoading</span><span class=\"token punctuation\">(</span><span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n <span class=\"token keyword\">return</span> loading <span class=\"token operator\">?</span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">Loading</span></span> <span class=\"token punctuation\">/></span></span> <span class=\"token operator\">:</span> list<span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">item</span> <span class=\"token operator\">=></span> ………<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h2 id=\"美好的未来-\" style=\"position:relative;\"><a href=\"#%E7%BE%8E%E5%A5%BD%E7%9A%84%E6%9C%AA%E6%9D%A5-\" aria-label=\"美好的未来  permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>美好的未来 ✨</h2>\n<p>React 用户应该都知道 <code class=\"language-text\">Suspense</code>，可惜的是它目前的用处仅仅是<strong>Code Splitting</strong>。似乎因为一些稳定性问题，React 还没有将它最强大的一面公布出来。当然这是后话，但是如果你看过\n<a href=\"https://www.youtube.com/watch?v=ByBPyMBTzM0&#x26;t=1368s\">Concurrent Rendering in React - Andrew Clark and Brian Vaughn - React Conf 2018</a> 或者这个 <a href=\"https://github.com/facebook/react/tree/master/fixtures/unstable-async/suspense\">Demo</a> ，就知道 <code class=\"language-text\">ConcurrentMode</code> 配合 <code class=\"language-text\">Suspense</code> 的 <code class=\"language-text\">maxDuration</code> 就能解决上面的问题。比如这样一个场景</p>\n<blockquote>\n<p>懒加载路由 A 的组件，500ms 内没有加载成功则显示 loading</p>\n</blockquote>\n<p>这个通过未来的 React 可以轻松做到</p>\n<div class=\"gatsby-highlight\" data-language=\"jsx\"><pre class=\"language-jsx\"><code class=\"language-jsx\"><span class=\"token keyword\">const</span> LazyHome <span class=\"token operator\">=</span> <span class=\"token function\">lazy</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token keyword\">import</span><span class=\"token punctuation\">(</span><span class=\"token string\">'./home'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token function-variable function\">App</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">ConcurrentMode</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n      // 省去一些 Route 相关代码\n      </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">Route</span></span>\n        <span class=\"token attr-name\">path</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/<span class=\"token punctuation\">\"</span></span>\n        <span class=\"token attr-name\">render</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token parameter\">props</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">Suspense</span></span> <span class=\"token attr-name\">maxDuration</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token number\">500</span><span class=\"token punctuation\">}</span></span> <span class=\"token attr-name\">fallback</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">BeautifulLoading</span></span> <span class=\"token punctuation\">/></span></span><span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n            </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">LazyHome</span></span> <span class=\"token spread\"><span class=\"token punctuation\">{</span><span class=\"token punctuation\">...</span><span class=\"token attr-value\">props</span><span class=\"token punctuation\">}</span></span> <span class=\"token punctuation\">/></span></span><span class=\"token plain-text\">\n          </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span><span class=\"token class-name\">Suspense</span></span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span></span>\n      <span class=\"token punctuation\">/></span></span><span class=\"token plain-text\">\n    </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span><span class=\"token class-name\">ConcurrentMode</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>这个例子其实和我们要讨论的不一样，上面的例子是关于 Code Splitting，我们主要讨论的是 Ajax 请求的。</p>\n<p>不过强大的 React 也有方式做到后者，通过 <code class=\"language-text\">react-cache</code> 配合 <code class=\"language-text\">Suspense</code></p>\n<div class=\"gatsby-highlight\" data-language=\"jsx\"><pre class=\"language-jsx\"><code class=\"language-jsx\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> createResource <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'react-cache'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> fakeResource <span class=\"token operator\">=</span> <span class=\"token function\">createResource</span><span class=\"token punctuation\">(</span>fakeAPI<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> <span class=\"token function-variable function\">List</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> list <span class=\"token operator\">=</span> fakeResource<span class=\"token punctuation\">.</span><span class=\"token function\">read</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> list<span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">item</span> <span class=\"token operator\">=></span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>p</span> <span class=\"token attr-name\">key</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>item<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">{</span>item<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">}</span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>p</span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token function-variable function\">App</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">ConcurrentMode</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n      </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">Suspense</span></span> <span class=\"token attr-name\">maxDuration</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token number\">500</span><span class=\"token punctuation\">}</span></span> <span class=\"token attr-name\">fallback</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">BeautifulLoading</span></span> <span class=\"token punctuation\">/></span></span><span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n        </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">List</span></span> <span class=\"token punctuation\">/></span></span><span class=\"token plain-text\">\n      </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span><span class=\"token class-name\">Suspense</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n    </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span><span class=\"token class-name\">ConcurrentMode</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>它可以在 500ms 之内没有接受到服务器响应的话，显示 loading，否则直接渲染 list。</p>\n<p>根据<a href=\"https://reactjs.org/blog/2018/11/27/react-16-roadmap.html#tldr\">官方的说法</a>，等到 <code class=\"language-text\">ConcurrentMode</code> 的稳定，至少也要到 2019 年的 Q2，而客户端级别的 <code class=\"language-text\">Suspense</code> 要到 Q3。</p>\n<h2 id=\"总结\" style=\"position:relative;\"><a href=\"#%E6%80%BB%E7%BB%93\" aria-label=\"总结 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>总结</h2>\n<p>本文主要介绍了使用 <code class=\"language-text\">Promise.race</code> 来解决 loading 闪烁的问题。同时也揭露了未来 React 可以带来的声明式的写法，以及如何在 <code class=\"language-text\">Code Splitting</code> 中解决同样的问题。</p>","frontmatter":{"title":"解决 loading 闪烁的问题","spoiler":"无条件 loading 是有代价的","image":null,"date":"2019/02/23"}}},"pageContext":{"slug":"/solve-fresh-of-loading/","previous":{"fields":{"slug":"/division100/division100/"},"frontmatter":{"title":"为什么要除以 100?","image":null,"spoiler":"一个整数除以 100 得到的浮点数，是否会丢失进度"}},"next":{"fields":{"slug":"/toutiao/"},"frontmatter":{"title":"头条的对与错","image":null,"spoiler":"头条没我想的那么坏"}}}}}