The rewritten title is: Calling React Promise.all().then() before each setState from promise.then() is rendered
P粉162773626
P粉162773626 2023-08-20 11:02:53
0
1
425
<p>I'm trying to make 200 calls to a remote resource to display in my table, while displaying a progress bar to show the number of calls remaining. </p> <p>Use this example to demonstrate how to use <code>Fetch()</code> and <code>Promise.all()</code> to call <code>setState()</ code> to update new data. </p> <p>My problem is with each promise's <code>.then()</code>, which calculates some logic and then calls <code>setState()</code> to update the data. </p> <p>My progress bar uses <code>Object.keys(data).length</code> to show progress. </p> <p>After <code>Promise.all()</code> triggers the "Complete" state, removing the progress bar, the promises themselves are still calling their <code>then()</code> , which causes the progress bar to be hidden before all resolved promises are displayed. </p> <p>How to deal with this problem correctly? </p> <hr /> <p>Demo, use <code>setTimeout()</code> to simulate expensive logic. </p> <p>The problem is that <code>Promise.all.then: 20</code> should be after <code>Render 20</code>. </p> <pre class="brush:none;toolbar:false;">Render 0 ... Render 12 Promise.all.then: 20 # I need this to be recorded after each Render Render 13 ... Render 19 Render 20 </pre> <p>To make the demo show the problem, the progress bar was removed (turned red) before it was completely filled.</p> <p><br /></p> <pre class="brush:js;toolbar:false;">const { useState } = React; const Example = () => { const [done, setDone] = useState(false); const [data, setData] = useState({}); const demoData = Array.from(Array(20).keys()); const demoResolver = (x) => new Promise(res => setTimeout(() => res(x), Math.random() * 1250)) const loadData = () => { const promises = demoData.map(c => demoResolver(c)); promises.forEach(promise => { promise .then(r => { setTimeout(() => { setData(p => ({ ...p, [r]: r })); }, 500); }) }); Promise.all(promises) .then(r => { console.log('Promise.all.then: ', r.length) setDone(true); }) } console.log('Render', Object.keys(data).length); const progressBarIsShownDebugColor = (done) ? 'is-danger' : 'is-info'; return ( <section className='section'> <h1 className='title is-3'>{'Example'}</h1> <progress max={demoData.length} value={Object.keys(data).length} className={'progress my-3 ' progressBarIsShownDebugColor} /> <button onClick={loadData}>Start</button> </section> ) } ReactDOM.render(<Example />, document.getElementById("react"));</pre> <pre class="brush:css;toolbar:false;">.as-console-wrapper { max-height: 50px !important; }</pre> <pre class="brush:html;toolbar:false;"><script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css"> <div id="react"></div></pre> <p><br /></p>
P粉162773626
P粉162773626

reply all(1)
P粉426780515

The problem shown in the code above is that after getting the data, there is an additional 500ms async delay before setting the state. In the actual code, it sounds like there is additional processing (probably synchronous) that causes setData to be called after .all.

The best practice is to have done as a computed property rather than a separate state, because at that point you don't need to rely on the state to set up contention, and Object.keys( data).length is cheap enough that it doesn't hurt performance (and you use it in other areas, you can cache it into a variable if it becomes a problem).

const [data, setData] = useState({});
const done = Object.keys(data).length === 20; // 在实际代码中为200

const { useState } = React;

const Example = () => {

    const [data, setData] = useState({});
    const done = Object.keys(data).length === 20; // 在实际代码中为200
      
    const demoData = Array.from(Array(20).keys());
    const demoResolver = (x) => new Promise(res => setTimeout(() => res(x), Math.random() * 1250))
    
    const loadData = () => {
        
        const promises = demoData.map(c => demoResolver(c));
          
        promises.forEach(promise => {
            promise
                .then(r => {
                    setTimeout(() => {
                        setData(p => ({ ...p, [r]: r }));
                    }, 500);
                })
        });
    }
    
    console.log('Render', Object.keys(data).length);
  
    const progressBarIsShownDebugColor = (done)
      ? 'is-danger'
      : 'is-info';
    
    return (
        <section className='section'>
            <h1 className='title is-3'>{'Example'}</h1>
            <progress 
                max={demoData.length}
                value={Object.keys(data).length} 
                className={'progress my-3 ' + progressBarIsShownDebugColor}
            />
            <button onClick={loadData}>Start</button>
        </section>
    )
}
ReactDOM.render(<Example />, document.getElementById("react"));
.as-console-wrapper { max-height: 50px !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<div id="react"></div>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template