Skip to main content
Code Review

Return to Answer

Commonmark migration
Source Link
deleted 12 characters in body
Source Link
timeoutFetch(url, {
 method: 'post',
 credentials: 'same-origin',
 timeout: 8000 // optional
}).then(checkResponseStatus)
.catch(, error => {
 // could be fetch() error or timeout error
})
timeoutFetch(url, {
 method: 'post',
 credentials: 'same-origin',
 timeout: 8000 // optional
}).then(checkResponseStatus)
.catch(error => {
 // could be fetch() error or timeout error
})
timeoutFetch(url, {
 method: 'post',
 credentials: 'same-origin',
 timeout: 8000 // optional
}).then(checkResponseStatus, error => {
 // could be fetch() error or timeout error
})
Source Link

Suggested ES6 rewrite

function timeoutFetch (input, init = {}) {
 const timeout = init && Number(init.timeout) || 8000
 return new Promise((resolve, reject) => {
 fetch(input, init).then(resolve, reject)
 setTimeout(() => reject(new TypeError('Client timed out')), timeout)
 })
}

Invocation

timeoutFetch(url, {
 method: 'post',
 credentials: 'same-origin',
 timeout: 8000 // optional
})
.then(checkResponseStatus)
.catch(error => {
 // could be fetch() error or timeout error
})

Explanation

function timeoutFetch (input, init = {})

Here, we are mimicking the signature of the native fetch API, which has one required argument and one optional argument. We simply expect a timeout property to optionally exist on init within this wrapper function.

const timeout = init && Number(init.timeout) || 8000

This first checks that init is "truthy", and then checks that init.timeout is a valid, non-zero number. If these are satisfied, then timeout is assigned the value of the supplied property, otherwise it defaults to 8000 milliseconds, like in your example.

return new Promise((resolve, reject) => { ... })

If you are at all familiar with using promises, then you'll recognize this pattern. While it is typically considered an anti-pattern, this particular implementation is written properly, and is also necessary in this case to take advantage of a convenient implicit race-condition behavior of promises that I'll explain in a moment.

fetch(input, init).then(resolve, reject)

This line invokes the native fetch() method with the wrapper's arguments and resolves the explicitly constructed promise with the fulfilled Response object, if, and only if, it is successful and it completes before the setTimeout() callback is invoked.

The reason this occurs is because of the specification: a promise can only be fulfilled, rejected, or remain pending forever, and if it is fulfilled or rejected, it is "settled" and cannot be fulfilled or rejected again.

If it is unsuccessful, and it fails before the timeout occurs, then it will invoke reject() with an error.

setTimeout(() => ...), timeout)

This part is pretty straightforward; we're creating a timeout given the amount of milliseconds supplied from init.timeout or the default 8000, and invoking the callback function in that amount of time.

reject(new TypeError('Client timed out'))

Inside the callback, we're rejecting the constructed promise with a TypeError, but keep in mind, if the fetch() function has already invoked resolve() or reject() in its then(), this call is essentially a noop because the constructed promise has already locked into its state and cannot be "settled" again. Because of this, it is unnecessary to assign a reference to the timeout and call clearTimeout() if those occur first.

Conclusion

If you've read this far, you've probably realized by now that the suggested code is compact because it takes advantage of a thorough understanding of the promise specification, and is able to safely make assumptions that keeps the code DRY.

default

AltStyle によって変換されたページ (->オリジナル) /