在对象内返回fetch .json。

时间:2018-02-08 03:20:21

标签: javascript promise fetch es6-promise

我有一个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
      }
    }
  )
}

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

3 个答案:

答案 0 :(得分: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会导致功能过多。如果你的函数看起来很难看并且等待它解决它,那么你的函数可能会开始做太多而且你没有解决潜在的问题。

想象一下你的json数据有日期,但json中的日期是字符串,你想提出请求并返回一个正文/状态对象,但是正文需要有实际日期。

以下是一个例子:

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

你可以说你需要一个功能:

  1. 从URL到响应承诺
  2. 承诺回应对象的承诺
  3. 从对象的承诺到具有实际日期的对象的承诺
  4. 来自回复承诺对象的实际日期以承诺身体/状态。
  5. 假设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
    5. 您可以使用承诺链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,它将使用thread创建的实用程序createThread获取响应元组(第1个函数的输出)和带日期的对象(第3个函数的输出) 1}}。

      &#13;
      &#13;
      //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)
      );
      &#13;
      &#13;
      &#13;

      getData的async await语法如下所示:

      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中硬编码函数了。

      Pipe(仍称为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]);
      

      这表明基于root行为很容易定义函数,这些函数相似但略有不同。 InitialLoad设置页面和过滤器(使用默认值),pageChanged仅设置页面并将过滤器保留为任何位置,filterChanges设置过滤器并将页面保留为原样。

      如何添加功能,例如不发出请求,而是从缓存中获取数据?

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

      以下是getData使用pipethread以及一系列函数的示例(在示例中,它们是硬编码的,但可以传入或导入)。

      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因此你不能创建一个在那里有函数的数组或者从a到b到c。

      在某些时候,我会在这里添加一个F#或ReasonML示例,它没有函数数组,而是一个模板函数,它将映射函数周围的包装器。

答案 1 :(得分:1)

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 可能会让它看起来更干净

答案 2 :(得分:0)

使用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响应。