Most client-side applications need to fetch data over the network. In the Ember.js ecosystem, many developers use a high-level abstraction, like Ember Data, that standardizes network access via an adapter pattern.
For those who want more low-level control over data fetching, it is
common to use techniques that are generally well-known in the wider web
development community. For example, because jQuery returns promises and
Ember's router understands promises, it's common for developers to use
$.ajax()
and its derivatives to fetch data:
model() {
return $.getJSON('/users.json');
}
(To improve integration with Ember's run loop and to fix deficiencies in jQuery's promise implementation, many Ember developers use ic-ajax instead: a small, API-comaptible shim around jQuery.)
This generally works well but suffers from two major problems motivating this proposal:
- jQuery,
ic-ajax
, etc. don't work in non-browser environments, such as FastBoot apps running in Node.js. - Sharing configuration between Ember Data and low-level network requests is difficult.
Many developers take a "mix and match approach," using Ember Data in most of their app but still dropping down to raw AJAX in cases where they want precise, programmatic control.
For these apps, it's important that they work correctly in FastBoot (where, presently, only Ember Data works). Additionally, often both the Ember Data adapter and low-level network requests share configuration concerns, like adding authentication headers to outbound requests.
I propose a three-layered approach for maximizing compatibility, reusability and developer convenience:
- Ember Data, an ORM where network access is managed via the adapter.
- Ember Ajax, a full-featured library for raw network access. This library provides idiomatic Ember APIs and additional ergonomics on top of the low-level APIs provided by the browser.
- Ember Fetch, a very low-level shim over the
window.fetch()
API, polyfilled in environments where it's not available.
This document discusses each layer in turn, from the bottom up.
The Fetch API provides an interface for fetching resources (e.g., across the network.) It will seem familiar to anyone who has used XMLHttpRequest, but the new API provides a more powerful and flexible feature set.
One of the explicit goals of Ember.js is to provide developers with the ability to use future features of the web platform today. For example, Ember developers have been using ES6 modules for more than a year, adopting them far earlier than most other web developers.
We create a community of early adopters by focusing on making adoption as easy as possible, and encourage migrating the entire community to the new way of doing things. This creates a "network effect" for adoption of the new feature.
We have the same opportunity to standardize on the future today with the
fetch()
API, a modern revamp of the stalwart XMLHttpRequest
.
Ember Fetch is designed to be as small, targeted and low-overhead as
possible. It simply polyfills the fetch()
API, including inside
Node.js (for FastBoot) and in browsers where it is not yet implemented.
This small, focused nature means that anyone writing a library that both requires network access and will be used by Ember apps can support FastBoot without worrying about bloating their dependencies.
Additionally, it gives us an easy way to tell people to begin
refactoring their code away from $.ajax()
and towards the more modern
fetch()
. Once the implementation of fetch()
is widespread, we can
drop the XMLHttpRequest
-based polyfill and the implementation of Ember
Fetch becomes tiny indeed.
Ember Ajax (already in existence) will be refactored to
depend on Ember Fetch and direct all network access through it. Instead,
it will focus on providing Ember-idiomatic APIs on top of the primitive
fetch()
to make working with the network easier for Ember developers.
Specifically, that includes:
- Shorthand methods (
get()
,put()
, etc.) - Custom headers attached to every request (AJAX preflight filters)
- Custom host configuration
- Error types (
InvalidError
,ForbiddenError
, etc) rather than status codes - Customizing response categorization (success or error)
For more, see ember-ajax.
The expectation would be that most Ember apps that previously used
$.ajax()
would instead use the Ember Ajax service.
Ember Data's built-in adapters would require and use Ember Ajax. This
gives adapter authors the full suite of ergonomic improvements on top of
fetch()
to use in their adapters.
Additionally, it means that any configuration developers made to Ember Ajax (how to categorize responses, custom auth headers, etc.) would all be inherited by Ember Data.
This change would make Ember Data work with FastBoot out of the box and we could eliminate the monkeypatch that is required currently.
While Ember Data sharing configuration with the ajax
service is
convenient and correct in many scenarios, there may be scenarios where
it is incorrect.
For example, imagine you are using Ember Data to talk to a new API on
api.example.com
. Your app still has some legacy UI that relies on an
older API which lives at api-legacy.example.com
. It would be
problematic if you could not set one host for your Ember Data adapter
and another as the default for the ajax
service.
One solution may be to allow the user to provide an override on the
adapter. For example, the Ember Data adapter would not specify a host by
default, falling back to the ajax
service's configuration. However, if
you set a host on the adapter, it would always specify that host
explicitly when it requested data.
I made https://github.com/kellyselden/ember-data-fetch-support that might help official ember-data fetch support.