-
-
Save noahgrant/d3ea7eb7e65e865a41165447c0a2e0b6 to your computer and use it in GitHub Desktop.
Backbone.ajax with window.fetch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'underscore'; | |
import Backbone from 'backbone'; | |
import {stringify} from 'qs'; | |
const backboneSync = Backbone.sync.bind(Backbone); | |
const MIME_TYPE_JSON = 'application/json'; | |
const MIME_TYPE_DEFAULT = 'application/x-www-form-urlencoded; charset=UTF-8'; | |
Backbone.sync = (method, model, options) => { | |
// Backbone creates an `xhr` property on the options object for its default | |
// xhr request made via jquery. Using window.fetch this becomes just a reference | |
// to a Promise, and not very useful. So here we attach a response object that we | |
// mutate directly with the request's response object. Note that we the | |
// original options object passed to fetch/save/destroy calls (and kept in | |
// closure) is not the same one passed to Backbone.ajax. It's a copy, and so | |
// we must modify the response object directly for it to be passed through. | |
options.response = {}; | |
return backboneSync(method, model, options); | |
}; | |
Backbone.ajax = function(options={}) { | |
var hasData = !!_.size(options.data), | |
hasBodyContent = !/^(?:GET|HEAD)$/.test(options.type) && hasData; | |
if (options.type === 'GET' && hasData) { | |
options.url += (options.url.indexOf('?') > -1 ? '&' : '?') + stringify(options.data); | |
} | |
return window.fetch(options.url, { | |
...options, | |
method: options.type, | |
headers: { | |
Accept: MIME_TYPE_JSON, | |
// these defaults are the same as jquery's: | |
// * only set contentType header if a write request and if there is body data | |
// * default to x-www-form-urlencoded. Backbone will pass 'application/json' | |
// (and JSON-stringify options.data) for save/destroy calls | |
...(hasBodyContent ? {'Content-Type': options.contentType || MIME_TYPE_DEFAULT} : {}), | |
...options.headers | |
}, | |
...(hasBodyContent ? { | |
// Backbone already JSON-stringifies data for save/destroy calls, but for any other | |
// contentful request, for example a .fetch with {type: 'POST}, we take care of it here | |
body: typeof options.data === 'string' ? | |
options.data : | |
options.contentType === MIME_TYPE_JSON ? | |
JSON.stringify(options.data) : | |
stringify(options.data) | |
} : {}) | |
}).then((res) => { | |
// make a copy of the response object and place it into the options | |
// `response` property we created before Backbone.sync. This will make it | |
// available in our success callbacks. Note that our error callbacks will | |
// have it, as well, but they will also get it directly from the rejected | |
// promise. we use _.extend instead of Object.assign because none of the | |
// Response properties are enumerable | |
_.extend(options.response, res); | |
// catch block here handles the case where the response isn't valid json, | |
// like for example a 204 no content | |
return res.json().catch(() => ({})) | |
.then((json) => res.ok ? json : Promise.reject(_.extend({}, res, {json}))); | |
}).then(options.success, options.error).then(options.complete || _.noop); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment