javascript React Promise.all().then()在每个setState之前从promise调用.then()被呈现

vjrehmav  于 2023-06-28  发布在  Java
关注(0)|答案(2)|浏览(145)

我尝试调用200个远程资源以显示在我的表中,并有一个进度条显示剩余的调用量。
使用this example介绍如何使用Fetch()Promise.all()一起使用新数据调用setState()
我的问题在于每个promise的.then(),它计算一些逻辑,然后调用setState()来更新数据。
我的进度条使用Object.keys(data).length来显示进度。
Promise.all()触发了'done'状态,移除了进度条之后,promise本身仍然在那里调用then(),这导致进度条在显示全部已解决的promise之前被隐藏。
正确的处理方法是什么?
演示,它使用setTimeout()来模拟昂贵的逻辑。
问题是Promise.all.then: 20应该在**Render 20之后变成**。

Render 0
...
Render 12
Promise.all.then:  20       # I need this to log after each Render
Render 13
...
Render 19
Render 20

为了使演示显示问题,进度条在完全填满之前被删除(变为红色)。

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"));
.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>
hfyxw5xn

hfyxw5xn1#

上面代码中显示的问题是,在设置状态之前获取数据之后,您有一个额外的500ms异步延迟。在真实的代码中,听起来类似于有额外的处理(可能是同步)导致在.all之后调用setData s。
最好的办法是让done成为一个计算属性,而不是一个单独的状态,因为在这一点上,你不需要依赖于状态设置竞争,而且Object.keys(data).length足够便宜,它不会降低性能(加上你在其他领域使用它,如果它成为一个问题,你可以把它缓存在一个变量中)。

const [data, setData] = useState({});
const done = Object.keys(data).length === 20; // 200 in the real code
const { useState } = React;

const Example = () => {

    const [data, setData] = useState({});
    const done = Object.keys(data).length === 20; // 200 in the real code
      
    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>
8zzbczxx

8zzbczxx2#

你的问题是,promise在回调被调用时已经被解析了,这意味着Promise.all总是在最后一个setTimeout的回调执行之前被解析。
如果你想保持延迟,我认为你应该把setTimeout放在demoResolver里面

const demoResolver = (x) => new Promise(res => {
  setTimeout(() => { // I guess this is to mock the delay of an api call
    setTimeout(() => {
      setData(p => ({ ...p, [x]: x })); 
      res(x);
    }, 500)
  }, Math.random() * 1250)
})

相关问题