【问题标题】:Return fetch .json inside object.返回 fetch .json 内部对象。
【发布时间】:2018-07-18 12:31:38
【问题描述】:

我有一个 API 调用函数,我想将 response.json() 内容和 response.status 一起返回到一个对象中。

像这样:

  const getData = data => {
  return fetch('/api_endpoint',{
      method: 'GET',
      headers: {
          'Content-type': 'application/json'
      }
  })
  .then(response => {
        return { 
                  body: response.json(), 
                  status: response.status 
               }
    })
}

问题在于 response.json() 是一个承诺,所以在解决之前我无法提取它的值。

我可以通过这样做来破解它:

  const getData = data => {
  let statusRes = undefined;
  return fetch('/api_endpoint',{
      method: 'GET',
      headers: {
          'Content-type': 'application/json'
      }
  })
  .then(response => {
        statusRes = response.status;
        return response.json()
    })
  .then(data => {
      return {
          body: data,
          status: statusRes
      }
    }
  )
}

但是感觉不对。有人有更好的主意吗?

【问题讨论】:

  • 这没有错,因为 response.json() 是一个承诺 - 不过,它可能会更好 - 注意:有时你需要“嵌套” .then's - 只是不要做一个金字塔厄运:p
  • 例如.then(response => response.json().then(body => ({ status: response.status, body})))

标签: javascript promise fetch es6-promise


【解决方案1】:

如果变量打扰你,则不需要它,你可以返回元组(ES中的数组)。

在这种情况下,变量足够保存,因为它只使用一次并且在同一个承诺堆栈中。

const getData = data => {
  return fetch('/api_endpoint',{
      method: 'GET',
      headers: {
          'Content-type': 'application/json'
      }
  })
  .then(response =>
    //promise all can receive non promise values
    Promise.all([//resolve to a "tuple"
      response.status,
      response.json()
    ])
  )
  .then(
    /**use deconstruct**/([status,body]) =>
    //object literal syntax is confused with
    //  function body if not wrapped in parentheses
      ({
          body,
          status
      })
  )
}

或者按照约瑟夫的建议去做:

const getData = data => {
  return fetch('/api_endpoint',{
      method: 'GET',
      headers: {
          'Content-type': 'application/json'
      }
  })
  .then(response =>
      response.json()
      .then(
        body=>({
          body,
          status:response.status
        })
      )
  )
}

更新

这里我想解释一下为什么使用 await 会导致函数做的太多。如果你的函数看起来很丑,然后用 await 解决,那么很可能你的函数一开始做的太多,而你没有解决根本问题。

假设你的 json 数据有日期,但 json 中的日期是字符串,你想发出请求并返回一个 body/status 对象,但 body 需要有真实的日期。

这方面的一个例子可以用以下来演示:

typeof JSON.parse(JSON.stringify({startDate:new Date()})).startDate//is string

你可以说你需要一个函数:

  1. 从 URL 到响应承诺
  2. 从响应的承诺到对象的承诺
  3. 从对象的承诺到实际日期的对象的承诺
  4. 从响应具有实际日期的对象的承诺到正文/状态的承诺。

说 url 是类型 a 并且响应的承诺是类型 b 等等。然后你需要以下内容:

a -> b -> c -> d ; [b,d]-> e

与其编写一个函数去a -> e,不如编写4个函数:

  1. a -> b
  2. b -> c
  3. c -> d
  4. [b,d] -> e

您可以使用 Promise 链 1.then(2).then(3) 将输出从 1 输送到 2 和从 2 输送到 3 问题是函数 2 得到一个您在函数 4 之前才使用的响应。

这是编写函数来执行类似a -> e 的常见问题,因为c -> d(设置实际日期)不关心响应,但[b,d] -> e 关心。

这个常见问题的解决方案可以是函数的线程化结果(我不确定函数式编程中这个的正式名称,如果你知道,请告诉我)。在函数式程序中,您有类型 (a,b,c,d,e) 和从类型 a 到 b,或从 b 到 c 的函数......对于 a 到 c,我们可以组合 a 到 b 和 b 到 c .但是我们还有一个函数可以从元组[b,d]e

如果您查看第 4 个函数 objectAndResponseToObjectAndStatusObject,它需要一个响应元组(第一个函数的输出)和带有日期的对象(第 3 个函数的输出),使用一个名为 thread 的实用程序使用 createThread 创建。

//this goes into a library of utility functions
const promiseLike = val =>
  (val&&typeof val.then === "function");
const REPLACE = {};
const SAVE = {}
const createThread = (saved=[]) => (fn,action) => arg =>{
  const processResult = result =>{
    const addAndReturn = result => {
      (action===SAVE)?saved = saved.concat([result]):false;
      (action===REPLACE)?saved = [result]:false;
      return result;  
    };
    return (promiseLike(result))
      ? result.then(addAndReturn)
      : addAndReturn(result)
  }
  return (promiseLike(arg))
    ? arg.then(
        result=>
          fn(saved.concat([result]))
      )
      .then(processResult)
    : processResult(fn(saved.concat([arg])))
};
const jsonWithActualDates = keyIsDate => object => {
  const recur = object =>
    Object.assign(
      {},
      object,
      Object.keys(object).reduce(
        (o,key)=>{
          (object[key]&&(typeof object[key] === "object"))
            ? o[key] = recur(object[key])
            : (keyIsDate(key))
                ? o[key] = new Date(object[key])
                : o[key] = object[key];
          return o;
        },
        {}
      )
    );
  return recur(object);
}

const testJSON = JSON.stringify({
  startDate:new Date(),
  other:"some other value",
  range:{
    min:new Date(Date.now()-100000),
    max:new Date(Date.now()+100000),
    other:22
  }
});

//library of application specific implementation (type a to b)
const urlToResponse = url => //a -> b
  Promise.resolve({
    status:200,
    json:()=>JSON.parse(testJSON)
  });
const responseToObject = response => response.json();//b -> c
const objectWithDates = object =>//c -> d
  jsonWithActualDates
    (x=>x.toLowerCase().indexOf("date")!==-1||x==="min"||x==="max")
    (object);
const objectAndResponseToObjectAndStatusObject = ([response,object]) =>//d -> e
  ({
    body:object,
    status:response.status
  });

//actual work flow
const getData = (url) => {
  const thread = createThread();
  return Promise.resolve(url)
  .then( thread(urlToResponse,SAVE) )//save the response
  .then( responseToObject )//does not use threaded value
  .then( objectWithDates )//does no use threaded value
  .then( thread(objectAndResponseToObjectAndStatusObject) )//uses threaded value
};
getData("some url")
.then(
  results=>console.log(results)
);

getData 的异步等待语法如下所示:

const getData = async (url) => {
  const response = await urlToResponse(url);
  const data = await responseToObject(response);
  const dataWithDates = objectWithDates(data);
  return objectAndResponseToObjectAndStatusObject([response,dataWithDates]);
};

你可以问自己getData 没有做太多吗?不,getData 实际上并没有实现任何东西,它正在组合具有将 url 转换为响应、响应数据的实现的函数...... GetData 只是用实现组合函数。

为什么不使用闭包

您可以编写getData 的非异步语法,并在闭包中提供响应值,如下所示:

const getData = (url) => 
  urlToResponse(url).then(
    response=>
      responseToObject(response)
      .then(objectWithDates)
      .then(o=>objectAndResponseToObjectAndStatusObject([response,o]))
  );

这也很好,但是当你想将你的函数定义为一个数组并将它们通过管道创建新函数时,你不能再在 getDate 中硬编码函数。

管道(仍称为 compose here)将一个函数的输出作为输入传递给另一个函数。让我们尝试一个管道示例,以及如何使用它来定义执行类似任务的不同函数,以及如何修改根实现而不根据它更改函数。

假设您有一个具有分页和过滤功能的数据表。最初加载表时(行为的根定义),您将参数页面值设置为 1 和一个空过滤器,当页面更改时,您只想设置页面部分参数,当过滤器更改时,您只想设置过滤器部分参数。

所需的功能是:

const getDataFunctions = [
  [pipe([setPage,setFiler]),SET_PARAMS],
  [makeRequest,MAKE_REQUEST],
  [setResult,SET_RESULTS],
];

现在,您可以将初始加载行为作为函数数组进行。初始加载如下所示:

const initialLoad = (action,state) =>
  pipe(getDataFunctions.map(([fn])=>fn))([action,state]);

页面和过滤器更改如下所示:

const pageChanged = action =>
  pipe(getDataFunctions.map(
    ([fn,type])=>{
      if(type===SET_PARAMS){
        return setPage
      }
      return fn;
    }
  ))([action,state]);
const filterChanged = action =>
  pipe(getDataFunctions.map(
    ([fn,type])=>{
      if(type===SET_PARAMS){
        return setFiler
      }
      return fn;
    }
  ))([action,state]);

这演示了基于相似但略有不同的根行为轻松定义函数。 InitialLoad 设置页面和过滤器(使用默认值),pageChanged 只设置页面和离开过滤器的任何内容,filterChanges 设置过滤器和离开页面的任何内容。

添加诸如不发出请求但从缓存中获取数据之类的功能怎么样?

const getDataFunctions = [
  [pipe([setPage,setFiler]),SET_PARAMS],
  [fromCache(makeRequest),CACHE_OR_REQUEST],
  [setResult,SET_RESULTS],
];

这是一个使用 pipethread 和函数数组的 getData 示例(在示例中,它们是硬编码的,但可以传入或导入)。

const getData = url => {
  const thread = createThread();
  return pipe([//array of functions, can be defined somewhere else or passed in
    thread(urlToResponse,SAVE),//save the response
    responseToObject,
    objectWithDates,
    thread(objectAndResponseToObjectAndStatusObject)//uses threaded value
  ])(url);
};

函数数组对于 JavaScript 来说很简单,但对于静态类型语言来说就有点复杂了,因为数组中的所有项都必须是 T->T,因此你不能创建一个数组,其中包含线程或 go从a到b到c。

有时我会在此处添加一个 F# 或 ReasonML 示例,它没有函数数组,而是一个模板函数,它将映射函数的包装器。

【讨论】:

    【解决方案2】:

    使用async/await。这会让事情变得更干净:

    async function getData(endpoint) {
      const res = await fetch(endpoint, {
        method: 'GET'
      })
    
      const body = await res.json()
    
      return {
        status: res.status,
        body
      }
    }
    

    您可能还想添加 try / catch 块和 res.ok 检查来处理任何请求错误或非 20x 响应。

    【讨论】:

      【解决方案3】:
      const getData = data => {
        return fetch('/api_endpoint',{
            method: 'GET',
            headers: {
                'Content-type': 'application/json'
            }
        })
        .then(async response => {
              return { 
                        body: await response.json(), 
                        status: response.status 
                     }
          })
      }
      

      es6 async/await 可能会让它看起来更干净

      【讨论】:

        猜你喜欢
        • 2020-07-09
        • 2018-11-19
        • 2017-11-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-03
        相关资源
        最近更新 更多