Investigué sobre SO y encontré muchas preguntas/respuestas similares, pero es posible que me falte algo en mi comprensión básica sobre cómo trabajar con esta pila.
Estoy trabajando en un proyecto nativo de reacción junto con RXJS/observables. En algún momento hago descargas de archivos, esta parte no es un problema. Una combinación de axios-rxjs y react-native-file-system preexistentes me llevan a donde quiero. El problema es que no estoy seguro de cómo manejarlo limpiamente sin async/await lo que entiendo es un antipatrón.
Quiero transformar este código de trabajo en un flujo limpio de estilo observable.
Implementé un Epic que hace las operaciones que quiero como tales:
const myDownloadEpic = ( action$, state$ ) => action$.pipe( ofType(myDownloadActionType), // catches the relevant action map(action => action.payload), mergeMap(payload => downloadManager // a class with the relevant utils to get files .download(payload), // this axios call returns my Blob file as Observable<Blob> .pipe( mergeMap(async response => { // a promise is returned by RNFS to read the contents of a folder const files = await RNFS.readDir(RELEVANT_TARGET_PATH) ... // a promise returned from a custom function that converts my blob to bas64 const data = await convertToBase64(response) // another promise returned by RNFS to write to the file system await RNFS.writeFile(FULL_PATH, data, 'base64'); ... }) ) ) )Intenté dividir esto en varias canalizaciones, por ejemplo, intenté dividir la operación READ en una canalización anterior, pero termina pareciendo muy detallado. ¿No hay una forma simple y limpia de "esperar" hasta que se cumplan las promesas para poder tomar decisiones basadas en su resultado?
¿Qué se consideraría más limpio en esta situación?
El operador from() puede convertir una promesa en un observable que emitirá el valor prometido y luego se completará.
Si necesita esperar hasta que se resuelvan todas las promesas, recomiendo forkJoin() ya que no emitirá un valor hasta que se completen todos los observables.
Por último, para que el código sea un poco más limpio, también recomendaría declarar variables/funciones separadas para definir sus observables para cada promesa.
const files$ = from(RNFS.readDir(RELEVANT_TARGET_PATH)); const getData = (response: unknown) => from(convertToBase64(response)).pipe( mergeMap(data=> from(RNFS.writeFile(FULL_PATH, data, 'base64')).pipe( mapTo(data) ) ) ); const myDownloadEpic = (action$, state$) => action$.pipe( ofType(myDownloadActionType), map(({ payload }) => payload), mergeMap(payload => downloadManager.download(payload)), mergeMap(response => forkJoin({ files: files$, data: getData(response) }) ), map(({ files, data }) => { // check something with `files` and `data` }) ); Supongo que la respuesta RNFS.writeFile() es void , así que la pongo como un efecto cuando me suscribo a getData() . El operador mapTo() ignora cualquier valor emitido desde la fuente observable y devuelve cualquier valor que ingrese en el parámetro.
Puedes probar algo como esto. Debería ser más o menos equivalente a lo que ha escrito anteriormente.
const myDownloadEpic = ( action$, state$ ) => action$.pipe( ofType(myDownloadActionType), map(action => action.payload), mergeMap(payload => downloadManager.download(payload)), mergeMap(response => from(RNFS.readDir(RELEVANT_TARGET_PATH)).pipe( map(files => ({response, files})) )), mergeMap(values => from(convertToBase64(values.response)).pipe( map(data => ({...values, data})) )), mergeMap(({response, files, data}) => RNFS.writeFile(FULL_PATH, data, 'base64')) );