Redux 传奇、axios 和进度事件

IT技术 reactjs redux redux-saga
2021-05-18 11:28:47

是否有干净/简短/正确的方法来一起使用 axios Promise和上传进度事件?

假设我有下一个上传功能:

function upload(payload, onProgress) {
  const url = '/sources/upload';

  const data = new FormData();

  data.append('source', payload.file, payload.file.name);

  const config = {
    onUploadProgress: onProgress,
    withCredentials: true
  };

  return axios.post(url, data, config);
}

这个函数返回了Promise。

我还有一个传奇:

function* uploadSaga(action) {
  try {
    const response = yield call(upload, payload, [?? anyProgressFunction ??]);
    yield put({ type: UPLOADING_SUCCESS, payload: response });
  } catch (err) {
    yield put({ type: UPLOADING_FAIL, payload: err });
  }
}

我想接收进度事件并将其放在 saga 中。我还想捕获 axios 请求的成功(或失败)结果。是否可以?

谢谢。

2个回答

所以我找到了答案,感谢Mateusz Burzyński的澄清。

我们需要使用 eventChannel,但有点巧妙。

假设我们有上传文件的 api 函数:

function upload(payload, onProgress) {
  const url = '/sources/upload';

  const data = new FormData();

  data.append('source', payload.file, payload.file.name);

  const config = {
    onUploadProgress: onProgress,
    withCredentials: true
  };

  return axios.post(url, data, config);
}

在 saga 中,我们需要创建 eventChannel,但将发射放在外面。

function createUploader(payload) {

  let emit;
  const chan = eventEmitter(emitter => {

    emit = emitter;
    return () => {}; // it's necessarily. event channel should 
                     // return unsubscribe function. In our case 
                     // it's empty function
  });

  const uploadPromise = upload(payload, (event) => {
    if (event.loaded.total === 1) {
      emit(END);
    }

    emit(event.loaded.total);
  });

  return [ uploadPromise, chan ];
}

function* watchOnProgress(chan) {
  while (true) {
    const data = yield take(chan);
    yield put({ type: 'PROGRESS', payload: data });
  }
}

function* uploadSource(action) {
  const [ uploadPromise, chan ] = createUploader(action.payload);
  yield fork(watchOnProgress, chan);

  try {
    const result = yield call(() => uploadPromise);
    put({ type: 'SUCCESS', payload: result });
  } catch (err) {
    put({ type: 'ERROR', payload: err });
  }
}

我个人发现接受的答案非常复杂,而且我很难实现它。其他 google / SO 搜索都导致类似类型的答案。如果它对你有用,那很好,但我找到了另一种使用EventEmitter我个人认为更简单的方法。

在代码中的某处创建一个事件发射器:

// emitter.js

import { EventEmitter } from "eventemitter3";

export default new EventEmitter();

在您进行 api 调用的传奇中,使用此发射器在onUploadProgress回调中发出一个事件

// mysagas.js
import eventEmitter from '../wherever/emitter';

function upload(payload) {
  // ...

  const config = {
    onUploadProgress: (progressEvent) = {
      eventEmitter.emit(
        "UPLOAD_PROGRESS", 
        Math.floor(100 * (progressEvent.loaded / progressEvent.total))
      );
    }
  };

  return axios.post(url, data, config);
}

然后在需要此上传进度号的组件中,您可以在挂载时监听此事件:

// ProgressComponent.jsx
import eventEmitter from '../wherever/emitter';

const ProgressComponent = () => {

  const. [uploadProgress, setUploadProgress] = useState(0);

  useEffect(() => {
    eventEmitter.on(
      "UPLOAD_PROGRESS",
      percent => {
        // latest percent available here, and will fire every time its updated
        // do with it what you need, i.e. update local state, store state, etc
        setUploadProgress(percent)
      }
    );
  
    // stop listening on unmount
    return function cleanup() {
      eventEmitter.off("UPLOAD_PROGRESS")
    }
  }, [])

  return <SomeLoadingBar value={percent} />

}

这对我有用,因为我的应用程序已经eventEmitter出于其他原因使用了全局变量。我发现这更容易实现,也许其他人也会这样做。