Estoy usando Chainlink Oracles para obtener números aleatorios. Este proceso de obtener un número aleatorio lleva un tiempo. Para hacer eso, tengo que ejecutar esta línea (llamada asíncrona), que llama a una función de mi contrato inteligente y almacena el nuevo número aleatorio:
const newRandomNumber = await contract.connect(signer).randomResult();Lo que quiero es automatizar este proceso: cuando se genera un nuevo número (y está disponible para su uso), me gustaría mostrar un mensaje al usuario (en mi fronted).
Oye, se ha generado un nuevo número.
¿Cómo podría verificar periódicamente si el resultado almacenado en newRandomNumber ha cambiado? ¿Y cómo podría mostrar un mensaje cuando eso ha sucedido?
Cree un Observable para llamar periódicamente a un punto final de API:
import { distinctUntilChanged, switchMap, timer } form 'rxjs'; /* Use RxJS library for convenience */ const getRandomNumber = contract.connect(signer).randomResult(); const observable = timer(0, 1000) /* Trigger now and each 1000 ms */ .pipe( switchMap(getRandomNumber), /* On each run, get the current number */ distinctUntilChanged(), /* Only trigger subscribers if new number */ ) const subscription = observable.subscribe((number) => console.log(number)); /* Don't forget to unsubscribe after, eg when the component unmounts */ subscription.unsubscribe(); El patrón de observador le permite recibir notificaciones (es decir, 'suscribirse') a múltiples eventos asíncronos, de la misma manera que una Promise de JavaScript le notifica después de que finaliza un único evento asíncrono. (Los observadores son en realidad mucho más flexibles que eso, este es solo un caso de uso)
Veamos cómo podríamos implementar su comportamiento deseado en javascript básico
let currentRandomNumber = null; let isCanceled = false; async function checkAndUpdateNumber() { while (!isCanceled) { /* Set your new (or same) number */ currentRandomNumber = await contract.connect(signer).randomResult(); /* Wait 1000ms */ await new Promsie((resolve) => setTimeout(resolve, 1000)); } } checkAndUpdateNumber(); /* If you want to stop the loop */ isCancelled = true;Esta implementación funciona pero deja mucho margen de mejora. El código no es reutilizable en lo más mínimo y no es fácilmente comprobable.
Los observadores le brindan una interfaz mucho más limpia para manejar múltiples operaciones asincrónicas. Consulte este artículo para tener una idea de cómo trabajan los observadores bajo el capó.
La biblioteca de javascript para observables es RxJS . Está bien probado y proporciona innumerables métodos de utilidad, por lo que le recomiendo que lo compruebe.
Debido a que etiquetó React en su pregunta, aquí hay un ejemplo comentado usando el estado React:
<div id="root"></div><script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/standalone@7.16.6/babel.min.js"></script> <script type="text/babel" data-type="module" data-presets="env,react"> const {useEffect, useState} = React; // simulate making a network request to get a random number from an API: // it will return a promise which will (at some point in the future) // resolve with the random number, or reject with an Error function mockFetchRandomNumber () { const randomNumber = Math.floor(Math.random() * 1000) + 1; // 1-1000 const delay = Math.floor(Math.random() * 1000) + 1000; // 1-2s const chance = 0.2; // percent chance of random network failure return new Promise((resolve, reject) => setTimeout(() => { if (chance > Math.random()) reject(new Error('Network error')); else resolve(randomNumber); }, delay)); } // a custom hook to wrap the logic of fetching the random number // and maintain the state of the request while doing so function useRandomNumber () { const [data, setData] = useState(); // the potential data const [error, setError] = useState(); // a potential error const [isLoading, setIsLoading] = useState(false); // whether the request is still pending const [reloadBoolean, setReloadBoolean] = useState(false); useEffect(() => { const fetchData = async () => { setIsLoading(true); setData(undefined); setError(undefined); try { const randomNumber = await mockFetchRandomNumber(); setData(randomNumber); setError(undefined); } catch (ex) { setError(ex instanceof Error ? ex : new Error(String(ex))); setData(undefined); } setIsLoading(false); }; fetchData(); }, [reloadBoolean, setData, setError, setIsLoading]); return { data, error, isLoading, // will toggle the value of `reloadBoolean`, forcing a re-render reload: () => setReloadBoolean(b => !b), }; } function Example () { const {data, error, isLoading, reload} = useRandomNumber(); return ( <div> { isLoading ? (<div>Loading random number...</div>) : null } { error ? (<div>There was an error loading the data ({error.message})</div>) : null } { data ? (<div>The random number is: {data}</div>) : null } <button onClick={reload}>Get new number</button> </div> ); } ReactDOM.render(<Example />, document.getElementById('root')); </script>