当 return 代码不是 200 时如何抛出响应?

How to throw a Response when the return code is not 200?

我正在为我的 API 调用编写一个助手,希望它能

原因是我有一个 API 调用的案例,其中预期 404 并且想要管理这个特定案例。其他404(在不同的调用中)确实是错误的。

考虑以下代码:

const apiCall = (url) => {
  return fetch(url)
    .then(r => {
      if (r.ok) {
        return r.text()
      } else {
        console.log('in apiCall → then(), fetch soft-failed, passing error downstream')
        throw Error(r.statusText) // this works but is not what I want, I want to throw "r"
      }
    })
    .catch(err => {
      console.log('in apiCall → catch(), passing error downstream')
      throw err
    })
}

apiCall('http://httpstat.us/200')
  .then(r => console.log(r))
  .catch(err => console.log(`the return code was ${err}`))  
  
apiCall('http://httpstat.us/404')
  .then(r => console.log(r))
  .catch(err => console.log(`the return code was ${err}`))  // this works but I obviously got a statusText and not an r

这输出

in apiCall → then(), fetch soft-failed, passing error downstream
in apiCall → catch(), passing error downstream
the return code was Error: Not Found
200 OK

想要做的是一次调用,其中需要404(在上面代码的上下文中这是不正确的代码)

apiCall('http://httpstat.us/404')
  .then(r => console.log(r))
  .catch(err => {
    if (err.Code == 404) {
      // it is OK
    } else {
      // it is not OK
    }
  })

对于任何非 2xx 响应不正确的另一个调用:

apiCall('http://httpstat.us/404')
  .then(r => console.log(r))
  .catch(err => {
   // not OK
  })

我怎样才能 throw 响应而不仅仅是文本?


换句话说,我想要的代码:

const apiCall = (url) => {
  return fetch(url)
    .then(r => {
      if (r.ok) {
        return r.text()
      } else {
        console.log('in apiCall → then(), fetch soft-failed, passing error downstream')
        throw r
      }
    })
    .catch(err => {
      console.log('in apiCall → catch(), passing error downstream')
      throw err  // I would need to manage actual errors (network, ...)        
  })
}

apiCall('http://httpstat.us/200')
  .then(r => console.log(r))
  .catch(err => console.log(`the return code was ${err.Code}`))  
  
apiCall('http://httpstat.us/404')
  .then(r => console.log(r))
  .catch(err => console.log(`the return code was ${err.Code}`))

但这会输出 undefined

in apiCall → then(), fetch soft-failed, passing error downstream
in apiCall → catch(), passing error downstream
the return code was undefined
200 OK

可以添加参数为expectedFailureCodes,失败后查看请求码。如果是意料之中的,那你可以随意处理。

import fetch from "node-fetch";


const apiCall = (url, expectedFailureCodes=[]) => {
    return fetch(url)
        .then(async res => {
            if (res.ok) return res
            else if (expectedFailureCodes.includes(res.status)) {
                return {
                    passedIntentionally: true,
                    res
                }
            }
            else throw new Error(JSON.stringify({
                status: res.status,
                body: await res.text()
            }))
        })
        .catch(err => {
            throw err
        })
}


apiCall("http://httpstat.us/404", [404]).then(res => {
    console.log(res)
})
apiCall("http://httpstat.us/404", ).catch(err => {
    console.log(err)
})
apiCall("http://httpstat.us/200", ).then(res => {
    console.log(res)
})

好吧,throwing r 工作正常,你只是记录了错误的 属性。可以通过 r.status:

访问状态代码

const apiCall = (url) => {
  return fetch(url)
    .then(r => {
      if (r.ok) {
        return r.text()
      } else {
        console.log('in apiCall → then(), fetch soft-failed, passing error downstream')
        throw r
      }
    })
    .catch(err => {
      console.log('in apiCall → catch(), passing error downstream')
      throw err  // I would need to manage actual errors (network, ...)        
  })
}

apiCall('http://httpstat.us/200')
  .then(r => console.log(r))
  .catch(err => console.log(`the return code was ${err.status}`))  
  
apiCall('http://httpstat.us/404')
  .then(r => console.log(r))
  .catch(err => console.log(`the return code was ${err.status}`))

该方法的唯一问题是,您的代码不知道如何处理“硬错误”(代码中的网络错误或运行时错误)。你可以用“鸭子打字方法”来识别那些(如果它有一个 status 属性,它必须是一个 Response 对象......),但更好的解决方案是有一个自定义错误 class:

class HTTPError extends Error{
  constructor(r){
    super(`HTTP ${r.status} error`)
    this.r = r
  }
}

const apiCall = (url) => {
  return fetch(url)
    .then(r => {
      if (r.ok) {
        return r.text()
      } else {
        console.log('in apiCall → then(), fetch soft-failed, passing error downstream')
        throw new HTTPError(r)
      }
    })
    .catch(err => {
      console.log('in apiCall → catch(), passing error downstream')
      throw err
    })
}

apiCall('http://httpstat.us/200')
  .then(r => console.log(r))
  .catch(err => {
    if(!(err instanceof HTTPError))
      throw err //Other error
    console.log(`the return code was ${err.r.status}`)
  })  
  
apiCall('http://httpstat.us/404')
  .then(r => console.log(r))
  .catch(err => {
    if(!(err instanceof HTTPError))
      throw err //Other error
    //You can access `r` via `err.r` here
    console.log(`the return code was ${err.r.status}`)
  })