【问题标题】:How to handle common fetch actions inside saga如何处理 saga 中的常见 fetch 操作
【发布时间】:2019-11-15 08:38:03
【问题描述】:

我正在开发一个使用 API 的网站。

问题

我所有的 API 传奇都是这样的:

export function* login(action) {
  const requestURL = "./api/auth/login"; // Endpoint URL
  //  Select the token if needed : const token = yield select(makeSelectToken());

  const options = {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + btoa(JSON.stringify({ login: action.email, password: action.password })),
    }
  };

  try {
    // The request helper from react-boilerplate
    const user = yield call(request, requestURL, options);
    yield put(loginActions.loginSuccess(user.token);
    yield put(push('/'));
  } catch (err) {
    yield put(loginActions.loginFailure(err.detailedMessage));
    yield put(executeErrorHandler(err.code, err.detailedMessage, err.key)); // Error handling
  }
}

我的所有 sagas 都有相同的模式:

  • 如果我需要在 saga 开始时调用私有函数,请选择令牌

const token = yield select(makeSelectToken());

  • 处理捕获部分的错误
export const executeErrorHandler = (code, detailedMessage, key) => ({
  type: HTTP_ERROR_HANDLER, status: code, detailedMessage, key
});

export function* errorHandler(action) {
  switch (action.status) {
    case 400:
      yield put(addError(action.key, action.detailedMessage));
      break;

    case 401:
      put(push('/login'));
      break;

    //other errors...
  }
}

export default function* httpError() {
  yield takeLatest(HTTP_ERROR_HANDLER, errorHandler);
}

我想出的解决方案

删除令牌部分和错误处理部分并将它们放入调用助手中:

export function* login(action) {
  const url = `${apiUrl.public}/signin`;

  const body = JSON.stringify({
    email: action.email,
    password: action.password,
  });

  try {
    const user = yield call(postRequest, { url, body });

    yield put(loginSuccess(user.token, action.email));
    yield put(push('/'));
  } catch (err) {
    yield put(loginFailure());
  }
}
// post request just call the default request with a "post" method
export function postRequest({ url, headers, body, auth = null }) {
  return request(url, 'post', headers, body, auth);
}

export default function request(url, method, headers, body, auth = null) {
  const options = { method, headers, body };

  return fetch(url, addHeader(options, auth)) // add header will add the token if auth == true
    .then(checkStatus)
    .then(parseJSON)
    .catch(handleError); // the error handler
}

function handleError(error) {
  if (error.code === 401) {
    put(push('/login')); // <-- Here this doesn't work
  }

  if (error.code == 400) {
    displayToast(error);
  }
}

function addHeader(options = {}, auth) {
  const newOptions = { ...options };
  if (!options.headers) {
    newOptions.headers = {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      ...options.headers,
    };
  }

  if (auth) {
    const token =  yield select(makeSelectToken()); // <-- here it doesn't work
    newOptions.headers.Authorization = `Bearer ${auth}`;
  }

  return newOptions;
}

我知道解决方案是在生成器函数、副作用、yield 调用/选择之间,但我尝试了很多方法都不起作用。例如,如果我将所有内容包装在生成器函数中,则在代码继续并调用 API 后执行令牌加载。

您的帮助将不胜感激。

【问题讨论】:

  • addHeader 不是生成器函数,所以我不相信你可以使用yield。
  • @MattSugden 我尝试将 addHeader 转换为生成器函数,然后也转换为请求函数。传奇仍在继续,无需等待选择令牌。
  • 那是因为你没有另一个收益率低于它吗?传奇没有下一项等待
  • @MattSugden 我尝试在 addHeader 中添加 const token = yield select(makeSelectToken());,然后调用 addHeader( options, auth).next().valuefetch 中。但是,addHeader 没有在里面有带有令牌的对象,而是在不等待令牌的情况下返回了对象。

标签: reactjs fetch generator redux-saga yield


【解决方案1】:

您需要从生成器函数运行任何和所有效果(例如yield select),因此您需要生成器一直到调用堆栈中产生效果的位置。鉴于我会尽量将这些电话推高。我假设除了postRequest 之外,您可能还有getRequestputRequest 等,所以如果您想避免重复yield select,您需要在request 中进行操作。我无法完全测试您的 sn-p,但我相信这应该可行:

export function* postRequest({ url, headers, body, auth = null }) {
  return yield call(request, url, 'post', headers, body, auth); // could yield directly but using `call` makes testing eaiser
}

export default function* request(url, method, headers, body, auth = null) {
  const options = { method, headers, body };
  const token = auth ? yield select(makeSelectToken()) : null;
  try {
      const response = yield call(fetch, url, addHeader(options, token));
      const checkedResponse = checkStatus(response);
      return parseJSON(checkedResponse);
  } catch (e) {
     const errorEffect = getErrorEffect(e); // replaces handleError
     if (errorEffect) {
        yield errorEffect;
     }
  }
}

function addHeader(options = {}, token) {
  const newOptions = { ...options };
  if (!options.headers) {
    newOptions.headers = {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      ...options.headers,
    };
  }

  if (token) {
    newOptions.headers.Authorization = `Bearer ${token}`;
  }

  return newOptions;
}

function getErrorEffect(error) {
  if (error.code === 401) {
    return put(push('/login')); // returns the effect for the `request` generator to yeild
  }

  if (error.code == 400) {
    return displayToast(error); // assuming `displayToast` is an effect that can be yielded directly
  }
}

【讨论】:

  • 在try catch部分,response是一个promise,我不能做checkStatus(status)。如何在 checkedResponse 中访问响应的值?
  • 找到了。解决方案是将 promise fetch 调用包装在一个函数中,然后 yield 调用这个函数。 function fetcher(url, options, auth) { return fetch(url, addHeader(options, auth)).then(checkStatus).then(parseJSON); } 然后 return yield call(fetcher, url, options, token);
猜你喜欢
  • 2020-05-08
  • 2017-02-21
  • 1970-01-01
  • 1970-01-01
  • 2017-09-12
  • 2012-10-12
  • 2017-06-07
  • 2013-11-10
  • 2013-04-01
相关资源
最近更新 更多