简体   繁体   English

在对象内部返回fetch .json。

[英]Return fetch .json inside object.

I have an API calling function that I would like to return the response.json() content as well as the response.status together in a single object. 我有一个API调用函数,我想在单个对象中一起返回response.json()内容以及response.status。

Like so: 像这样:

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

The trouble is that response.json() is a promise, so I can't pull it's value until it's resolved. 麻烦的是response.json()是一个承诺,所以在解决之前我无法获取它的值。

I can hack around it by doing this: 我可以这样做:

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

But it just feels WRONG. 但这感觉很差。 Anybody have a better idea? 有人有更好的主意吗?

There is no need for the variable if it bothers you, you can return tuples (array in ES). 如果变量困扰您,则不需要它,您可以返回元组(ES中的数组)。

In this case variable is save enough since it's used only once and within the same promise stack. 在这种情况下,变量已足够保存,因为在同一个Promise堆栈中仅使用了一次。

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

Or do as Joseph suggested: 或按照约瑟夫的建议做:

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

update 更新

Here I would like to explain why using await can lead to functions that do too much. 在这里,我想解释一下为什么使用await会导致函数执行太多操作。 If your function looks ugly and solve it with await then likely your function was doing too much to begin with and you didn't solve the underlying problem. 如果您的函数看起来很丑陋并且需要等待解决,那么可能您的函数开始时做得太多,而您没有解决潜在的问题。

Imagine your json data has dates but dates in json are strings, you'd like to make a request and return a body/status object but the body needs to have real dates. 假设您的json数据中有日期,但是json中的日期是字符串,您想发出一个请求并返回一个body / status对象,但是该body需要有真实的日期。

An example of this can be demonstrated with the following: 可以通过以下示例演示此示例:

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

You could say you need a function that goes: 您可能会说您需要一个可以运行的函数:

  1. from URL to promise of response 从URL到响应的承诺
  2. from promise of response to promise of object 从回应的承诺到对象的承诺
  3. from promise of object to promise of object with actual dates 从对象的承诺到带有实际日期的对象的承诺
  4. from response and promise of object with actual dates to promise of body/status. 从对象的响应承诺到实际日期,再到承诺的身体/状态。

Say url is type a and promise of response is type b and so on and so on. 说url是类型a,响应的承诺是类型b,依此类推。 Then you need the following: 然后,您需要以下内容:

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

Instead of writing one function that goes a -> e it's better to write 4 functions: 与其编写一个去a -> e函数,不如编写四个函数:

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

You can pipe output from 1 into 2 and from 2 into 3 with promise chain 1.then(2).then(3) The problem is that function 2 gets a response that you don't use until function 4. 您可以使用诺言链1将输出从1 1.then(2).then(3)到2,将2 1.then(2).then(3)给3, 1.then(2).then(3) ,问题在于函数2会得到直到函数4才使用的响应。

This is a common problem with composing functions to do something like a -> e because c -> d (setting actual dates) does not care about response but [b,d] -> e does. 这是组成函数执行a- a -> e类的常见问题,因为c -> d (设置实际日期)并不关心响应,但[b,d] -> e却如此。

A solution to this common problem can be threading results of functions ( I'm not sure of the official name for this in functional programming, please let me know if you know ). 解决此常见问题的方法可能是对函数进行线程化( 我不确定函数式编程中此函数的正式名称,如果您知道,请告诉我 )。 In a functional style program you have types (a,b,c,d,e) and functions that go from type a to b, or b to c ... For a to c we can compose a to b and b to c. 在函数式程序中,您具有类型(a,b,c,d,e)和从类型a到b或从b到c的函数。对于a到c,我们可以将a组成b到b并将b组成c 。 But we also have a function that will go from tuple [b,d] to e 但是我们还有一个将元组[b,d]变为e的函数

If you look at the 4th function objectAndResponseToObjectAndStatusObject it takes a tuple of response (output of 1st function) and object with dates (output of 3rd function) using a utility called thread created with createThread . 如果你看看第四功能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) ); 

The async await syntax of getData would look like this: 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]);
};

You could ask yourself is getData not doing too much? 您可能会问自己是不是getData做得太多? No, getData is not actually implementing anything, it's composing functions that have the implementation to convert url to response, response to data ... GetData is only composing functions with the implementations. 不, getData实际上并没有实现任何东西,它是具有将url转换为响应,对数据的响应的实现的组合函数。GetData只是将函数与实现组合在一起。

Why not use closure 为什么不使用闭包

You could write the non async syntax of getData having the response value available in closure like so: 您可以编写getData的非异步语法,该语法的响应值可以在闭包中使用,如下所示:

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

This is perfectly fine as well, but when you want to define your functions as an array and pipe them to create new functions you can no longer hard code functions in getDate. 这也很好,但是当您要将函数定义为数组并将其通过管道创建新函数时,就不能再在getDate中对函数进行硬编码了。

Pipe (still called compose here ) will pipe output of one function as input to another. 管道( 在此仍称为compose)将管道将一个函数的输出作为另一函数的输入。 Let's try an example of pipe and how it can be used to define different functions that do similar tasks and how you can modify the root implementation without changing the functions depending on it. 让我们尝试一个管道的示例,以及如何使用它来定义执行相似任务的不同函数,以及如何在不更改其功能的情况下修改根实现。

Let's say you have a data table that has paging and filtering. 假设您有一个具有分页和过滤功能的数据表。 When table is initially loaded (root definition of your behavior) you set parameter page value to 1 and an empty filter, when page changes you want to set only page part of parameters and when filter changes you want to set only filter part of parameters. 最初加载表时(行为的根定义),请将参数页面值设置为1并设置一个空的过滤器;当页面更改时,您只希望设置参数的页面部分;当过滤器更改时,只希望设置参数的过滤器部分。

The functions needed would be: 所需的功能将是:

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

Now you have the behavior of initial loading as an array of functions. 现在,您具有作为函数数组进行初始加载的行为。 Initial loading looks like: 初始加载如下:

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

Page and filter change will look like: 页面和过滤器更改如下所示:

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]);

That demonstrates easily defining functions based on root behavior that are similar but differ slightly. 这表明可以轻松地基于根行为来定义相似但略有不同的函数。 InitialLoad sets both page and filter (with default values), pageChanged only sets page and leaves filter to whatever it was, filterChanges sets filter and leaves page to whatever it was. InitialLoad设置页面和过滤器(具有默认值),pageChanged仅设置页面并将过滤器保留为原样,filterChanges设置过滤器并将页面保留为原样。

How about adding functionality like not making the request but getting data from cache? 如何添加诸如不发出请求而是从缓存中获取数据之类的功能?

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

Here is an example of your getData using pipe and thread with an array of functions (in the example they are hard coded but can be passed in or imported). 这是使用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);
};

The array of functions is easy enough for JavaScript but gets a bit more complicated for statically typed languages because all items in the array have to be T->T therefor you cannot make an array that has functions in there that are threaded or go from a to b to c. 函数数组对于JavaScript来说足够容易,但是对于静态类型的语言来说,则变得更加复杂,因为数组中的所有项都必须为T->T因此您无法创建其中具有线程化或来自函数的函数的数组。从b到c。

At some point I'll add an F# or ReasonML example here that does not have a function array but a template function that will map a wrapper around the functions. 在某个时候,我将在此处添加一个F#或ReasonML示例,该示例不具有函数数组,而是一个模板函数,该模板函数将在函数周围映射包装器。

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 might help it look more clean es6 async / await可能会帮助它看起来更干净

Use async/await . 使用async/await That will make things much cleaner: 这将使事情变得更加干净:

async function getData(endpoint) {
  const res = await fetch(endpoint, {
    method: 'GET'
  })

  const body = await res.json()

  return {
    status: res.status,
    body
  }
}

You also might want to add a try / catch block and a res.ok check to handle any request errors or non 20x responses. 您可能还想添加一个try / catch块和一个res.ok检查来处理任何请求错误或非20x响应。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM