获取(fetch):用JSON错误对象拒绝(promise)。

67

我有一个HTTP API,无论成功或失败都会返回JSON数据。

一个失败的例子如下:

~ ◆ http get http://localhost:5000/api/isbn/2266202022 
HTTP/1.1 400 BAD REQUEST
Content-Length: 171
Content-Type: application/json
Server: TornadoServer/4.0

{
    "message": "There was an issue with at least some of the supplied values.", 
    "payload": {
        "isbn": "Could not find match for ISBN."
    }, 
    "type": "validation"
}

我在 JavaScript 代码中想要实现的是类似于这样的功能:

fetch(url)
  .then((resp) => {
     if (resp.status >= 200 && resp.status < 300) {
       return resp.json();
     } else {
       // This does not work, since the Promise returned by `json()` is never fulfilled
       return Promise.reject(resp.json());
     }
   })
   .catch((error) => {
     // Do something with the error object
   }

你的意思是 json 方法返回一个 Promise 吗? - thefourtheye
3
根据工作组的“fetch”规范:https://fetch.spec.whatwg.org/#concept-body-consume-body。 - jbaiter
5个回答

103
 // This does not work, since the Promise returned by `json()` is never fulfilled
return Promise.reject(resp.json());
好的,resp.json 所返回的 promise 一定会被执行,只不过 Promise.reject 不会等待它并且会立即拒绝该 promise。
我假设你实际上是想要做以下操作:
fetch(url).then((resp) => {
  let json = resp.json(); // there's always a body
  if (resp.status >= 200 && resp.status < 300) {
    return json;
  } else {
    return json.then(Promise.reject.bind(Promise));
  }
})

(或者,明确写出来)

    return json.then(err => {throw err;});

谢谢,那(几乎)起作用了!我不得不在一个匿名函数中包装Promise.reject,否则我会得到一个“undefined is not a function”错误,但是通过这个小改变它可以工作 :-) - jbaiter
嗯,你正在惰性加载 Promise 垫片吗?本地的 Promise.reject 不应该是未定义的。 - Bergi
2
在拒绝时?啊,它需要是.then(Promise.reject.bind(Promise)) - Bergi
我觉得 json.then(Promise.reject.bind(Promise)) 等同于 Promise.reject(json),或者我有什么遗漏吗? - user663031
1
@torazaburo:不,json在这里是一个promise,我们不想拒绝promise,而是拒绝它的结果值。 - Bergi
显示剩余5条评论

42

这里有一种更简洁的方法,依赖于response.ok并利用底层JSON数据,而不是.json()返回的Promise

function myFetchWrapper(url) {
  return fetch(url).then(response => {
    return response.json().then(json => {
      return response.ok ? json : Promise.reject(json);
    });
  });
}

// This should trigger the .then() with the JSON response,
// since the response is an HTTP 200.
myFetchWrapper('http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY').then(console.log.bind(console));

// This should trigger the .catch() with the JSON response,
// since the response is an HTTP 400.
myFetchWrapper('https://content.googleapis.com/youtube/v3/search').catch(console.warn.bind(console));


1
啊,.ok看起来很有趣。但我认为"底层的JSON数据"的使用并没有更加简洁清晰。毕竟,你可以将它简化为fetch(url).then(response => response.ok ? response.json() : response.json().then(err => Promise.reject(err))) - Bergi
我的意思是,可以不用 let json = resp.json(); 这行代码,因为 json 是一个 Promise。相反地,先解决 Promise,然后再使用它所解决的数据可能更加简单。两种方法都可以。 - Jeff Posnick
一直在尝试拒绝嵌套的 Promise,但不太确定该怎么做。结果发现只需调用静态的 "reject" 方法即可。在我看来,这比被接受的答案要好得多。 - Andris

12

我最喜欢上面的解决方案,来自Jeff Posnick,但嵌套方式相当丑陋。

使用较新的async/await语法,我们可以以更同步的方式完成操作,避免丑陋的嵌套,这可能会很快变得混乱。

async function myFetchWrapper(url) {
  const response = await fetch(url);
  const json = await response.json();
  return response.ok ? json : Promise.reject(json);
}

这有效的原因是,异步函数总是返回一个Promise,一旦我们获取了JSON,就可以根据响应状态(使用response.ok)决定如何返回它。

您可以像Jeff的答案中一样进行错误处理,但是您也可以使用try/catch、错误处理高阶函数,或者通过一些修改来防止Promise被拒绝,使用我最喜欢的技术来确保错误处理作为开发体验的一部分得到执行

const url = 'http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY'

// Example with Promises
myFetchWrapper(url)
  .then((res) => ...)
  .catch((err) => ...);

// Example with try/catch (presuming wrapped in an async function)
try {
  const data = await myFetchWrapper(url);
  ...
} catch (err) {
  throw new Error(err.message);
}

值得阅读的还有MDN - Checking that the fetch was successful,了解为什么我们必须这样做。简而言之,fetch请求只会因网络错误被拒绝,收到404并非网络错误。


0
也许这个选项是有效的。
new Promise((resolve, reject) => { 
    fetch(url)
    .then(async (response) => {
        const data = await response.json();
        return { statusCode: response.status, body: data };
    })
    .then((response) => {
        if (response.statusCode >= 200 && response.statusCode < 300) {
            resolve(response.body);
        } else {
            reject(response.body);
        }
    })
});

目前你的回答不够清晰,请[编辑]以添加更多细节,帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

0

我在MDN找到了解决方案:

function fetchAndDecode(url) {
  return fetch(url).then(response => {
    if(!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    } else {
      return response.blob();
    }
  })
}

let coffee = fetchAndDecode('coffee.jpg');
let tea = fetchAndDecode('tea.jpg');

Promise.any([coffee, tea]).then(value => {
  let objectURL = URL.createObjectURL(value);
  let image = document.createElement('img');
  image.src = objectURL;
  document.body.appendChild(image);
})
.catch(e => {
  console.log(e.message);
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接