【问题标题】:setInterval calling two async functions conditionallysetInterval 有条件地调用两个异步函数
【发布时间】:2021-02-23 04:44:53
【问题描述】:

我遇到了两个异步函数的问题。它们旨在从 API 顺序获取登录链接,但有时 Web 服务尚未准备好,因为它依赖于物理设备(可以重置),有时初始渲染会在服务启动之前发生。

因此,如果未填充 authConfigURLloginURL,则间隔每 2 秒运行两个异步函数,这不可能是最佳的,至少在当前状态下不是。

最初我有一个 useEffect() 和这两个函数并调用它们,将 loginURLauthConfigURL 传递给依赖数组。如果未填充 URL,我无法确定以特定时间间隔运行请求的正确方法。

如果不满足条件或请求的响应为空,是否有更好的方法来处理调用函数?

 function useInterval(callback, delay) {
    const savedCallback = useRef();

    useEffect(() => {
      savedCallback.current = callback;
    });

    useEffect(() => {
      function tick() {
        savedCallback.current();
      }

      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }, [delay]);
  }


  useInterval(() => {
    if (!authConfigURL || !loginURL) {
      setIsWaiting(true);
      fetchRootResources();
      fetchAuthenticationConfig();
    }
  }, 2000);

  async function fetchRootResources() {
    const rootURL = "/admin/api/rest/";
    try {
      const response = await fetch(rootURL, {
        method: "GET",
        headers: { Accept: "blah"},
      });
      if (!response.ok) {
        console.error("Handle response codes", response);
        setErrorMessage("Error fetching Root REST API Resources");
      }
      // if the response contains no data this will error out
      const data = await response.json();
      // set rootResources here
      setRootResources({
        time: data.links.time,
        // 10 or so other links
      });
      // this URL is needed in the next async function
      setAuthConfigURL(data.links.auth_config);
    } catch (error) {
      console.log("Error fetching root resources", error);
    }
  }

  async function fetchAuthenticationConfig() {
    // authConfigURL seems to be empty on initial render
    console.log("fetching authConfig with url: ", authConfigURL);
    try {
      const response = await fetch(authConfigURL, {
        method: "GET",
        headers: { Accept: "blah" },
      });
      if (!response.ok) {
        console.error("Handle response codes", response);
      }
      const data = await response.json();
      console.log(data);
      setLoginURL(data.data.links.login);
    } catch (error) {
      console.log("Error fetching auth config", error.message);
    }
  }

【问题讨论】:

  • 为什么不等待fetchRootResources 完成就调用fetchAuthenticationConfig?这将导致fetchAuthenticationConfig 始终使用在前一个间隔中获得的authConfigURL。听起来,如果失败的请求在 2 秒(或更多)秒后超时)重试失败的请求可能比敲击 API 直到获得两个成功的请求更好。
  • 所以从区间中删除fetchAuthenticationConfig()并从fetchRootResources()内部调用它是response.status === 200
  • 可能或fetchRootResources().then(fetchAuthenticationConfig).catch( ... handle errors or retry ...)。回想一下async 函数返回一个承诺。恕我直言,使用间隔计时器可能不是一个好的选择。
  • 您介意用.then() 举例回答吗?
  • 为了方便起见,我最终将fetch 调用放在同一个函数中。但是我确实使用了.then 子句将处理数据与获取数据分开。请参阅我的答案示例。

标签: javascript reactjs


【解决方案1】:

这篇文章着眼于在允许用户登录之前重试失败的尝试从服务器获取“root”和“授权”数据的替代方法。

  • 在概念上,连续尝试两个服务器请求,如果任何一个请求超时、失败或返回错误数据,则从头开始重试请求。重试逻辑支持在不使用间隔计时器的情况下设置最大重试次数和重试间隔时间。

  • 故意没有考虑处理服务器中断的最佳方式或在短时间内重试失败操作的实际好处等更广泛的问题。

  • 在这种情况下,可以使用各种技术和编码风格来提供错误处理。要改进工作代码或讨论个别方法的优点,请考虑在Code Review 上发帖以获取反馈。

  • 更新:包括从服务器获取响应的超时(编码为Promise.race([ fetch-request, expiry-timer]) 以匹配间隔计时器的行为。

回复评论的示例:

"use strict";

const getRootResources = rootURL =>  fetch(rootURL, {
    method: "GET",
    headers: { Accept: "blah"}
});
const getAuthConfig = authConfigURL => fetch(authConfigURL, {
    method: "GET",
    headers: { Accept: "blah" }
});
const ResponseError = new Error("Fetch response timeout");
const RootError = new Error("Error fetching Root REST API Resources");
const AuthError =  new Error("Failed to obtain config data from server");
const RetryError = new Error("An error occurred connecting to server, please retry again later");

const setupLogin = async () => {
  const rootURL = "https://www.example.com/admin/api/rest/";
  const maxRetry = 3;
  const retryMsec = 2000;
  const delay = async msec=> new Promise( resolve=>setTimeout(resolve, msec));
  const responseMsec = 2000;
  const expire = (msec,reason)=> new Promise((resolve,reject)=> setTimeout(reject, msec, reason));

  let rootData;
  let authData;

  for( var tryAgain = maxRetry; tryAgain; --tryAgain) {
    console.log("tries remaining: %s", tryAgain);
    let response = null;
    rootData = null;
    authData = null;
    try {
      response = await Promise.race( [
        getRootResources( rootURL),
        expire(responseMsec, ResponseError)
      ]);
      if (!response.ok) {
        console.error("Handle response codes", response);
        throw RootError;
      }
      rootData = await response.json(); // may throw

      const authConfigURL = rootData.links.auth_config;
      response = await Promise.race( [
        getAuthConfigURL( authConfigURL),
        expire(responseMsec, ResponseError)
      ]);
      if (!response.ok) {
        console.error("Handle response codes (config)", response);
        throw AuthError;
      }
      authData = await response.json();
      break;
    }
    catch(err) {
      console.error("Fetch or getting json failed");
      console.log(err);
      await delay(retryMsec);
      continue;
    }
  }
  if( tryAgain <= 0) {
    throw RetryError;
  }
  return {rootData, authData};
}

window.addEventListener("DOMContentLoaded", event => {
    setupLogin()
    .then( ({rootData, authData}) => {
      console.log("setupLogin() succeeded");
      // enable page login features:
      // setRootResources( rootData); // or as needed
      // setAuthConfig( authData);    // or as needed
      // ... blah
    })
    .catch( err=> {
       console.log("setupLogin() failed");
        console.error(err);
        // disable page login features and notify user
    });
});
Run to test that code compiles and reports failures to reach server.

请注意,setupLogin 会为 {rootData, authData} 返回一个承诺,并将处理它们留到承诺链的后面。

【讨论】:

    猜你喜欢
    • 2021-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-15
    • 1970-01-01
    • 2017-06-01
    • 1970-01-01
    • 2012-07-25
    相关资源
    最近更新 更多