我有一个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
}
}
)
}
但感觉不对劲。有人有更好的主意吗?
答案 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
你可以说你需要一个功能:
假设url是类型a,响应的承诺是类型b,依此类推。然后你需要以下内容:
a -> b -> c -> d ; [b,d]-> e
不是编写一个a -> e
的函数,而是编写4个函数更好:
a -> b
b -> c
c -> d
[b,d] -> e
您可以使用承诺链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}}。
//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;
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
使用pipe
和thread
以及一系列函数的示例(在示例中,它们是硬编码的,但可以传入或导入)。
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响应。