Estoy trabajando con React, y tengo un problema al sincronizar clics sucesivamente (eventos onClick):
Mi botón activa una función que llama a dos funciones resetViews y chargeViews ( chargeViews toma tiempo), así que cuando hago clic dos veces rápidamente:
resetView se ejecuta bien (establece viewsRef.current en nulo)chargeViews , pero como lleva tiempo, no actualizará viewsRef.current inmediatamente después.resetView pero viewsRef.current sigue siendo nulo (porque chargeViews aún no lo actualiza), por lo que no hará nada,chargeViewsresetView y dos ejecuciones retrasadas de chargeViews Lo que quiero es después de hacer clic, bloquear todo (incluso si el usuario hace clic, no hacemos nada) y ejecutar resetView , luego chargeViews y luego desbloquear para recibir otro clic y hacer el mismo trabajo.
<MyButton onClick={() => { resetViews(); chargeViews(); }}> Click me </MyButton>La función que lleva tiempo
const chargeViews = () => { if(!viewsRef.current){ ... reader.setUrl(url).then(()=>{ reader.loadData().then(()=>{ ... viewsRef.current = reader.getData(); })}) }}La función que se ignora si hago clic tan rápido (funciona bien si hago clic y espero un poco y luego vuelvo a hacer clic), pero si hago clic y vuelvo a hacer clic rápido, se ignora.
const resetViews = () => { if (viewsRef.current){ ... viewsRef.current = null; }}No estoy totalmente seguro de entender todo el tema... esto sería un comentario si no requiriera un texto tan largo.
De todos modos, en la medida en que necesite deshabilitar el botón una vez que se haya hecho clic, debe tratarlo al comienzo de su controlador onclick:
$(this.event.target).prop('disabled', true);y restablecerlo al final:
$(this.event.target).prop('disabled', false);En general, lo que hizo fue correcto en términos de llamar a una serie de funciones para que se ejecuten en cadena dentro del controlador. Pero esas 2 funciones parecen tener una invocación de promesa ... en ese caso, esas no se ejecutarán en cadena esperando que termine la primera.
Por lo tanto, debe pasar la segunda invocación como una devolución de llamada a la primera, para tener la oportunidad de llamarla SOLO una vez que la primera haya terminado su trabajo.
Espero que alguien vaya directamente al grano y sugiera cómo hacer que una función asíncrona "espere" para que, haga lo que haga, cuando se invoque, se esperará a que se complete antes de que se evalúe la siguiente declaración. Por lo general, solo es cuestión de agregar una await antes de su firma, pero hay algunas advertencias.
Primero, debe convertir sus funciones que utilizan Promise en funciones asíncronas y luego esperarlas cuando las invoque. Esto facilitará el control del orden de ejecución:
const chargeViews = async () => { if(!viewsRef.current){ ... await reader.setUrl(url); await reader.loadData(); ... viewsRef.current = reader.getData(); } }Luego, necesita una referencia isExecuting que será verdadera cuando se ejecuten otras invocaciones y falsa cuando ninguna se esté ejecutando actualmente:
const isExecuting = useRef(false); const handleClick = async () => { if (!isExecuting.current) { // block other clicks from performing actions in parallel isExecuting.current = true; try { resetViews(); await chargeViews(); } finally { // unblock other clicks isExecuting.current = false; } } }; Por último, use la función handleClick recién creada en su JSX:
<MyButton onClick={handleClick}> Click me </MyButton>