如何处理 saga 中的常见获取操作

How to handle common fetch actions inside saga

我正在开发一个 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
  }
}

我所有的故事都有相同的模式:

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 调用 / select 之间,但我尝试了很多东西都没有用。例如,如果我将所有内容都包装在生成器函数中,则在代码继续并调用 API.

之后执行令牌加载

我们将不胜感激。

您需要 运行 来自生成器函数的任何和所有效果(例如 yield select),因此您将需要生成器一直到调用堆栈中的 yield 点效果。鉴于我会尝试将这些呼叫推得尽可能高。我假设除了 postRequest 之外,您可能还有 getRequestputRequest 等,所以如果您想避免重复 yield select,您需要在 [=16] 中进行=].我无法完全测试您的代码段,但我相信这应该有效:

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
  }
}