forked from dontcallmedom/api-design-cookbook
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
266 lines (266 loc) · 12.4 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'/>
<title>Web API Design Cookbook</title>
<script class='remove'>
var respecConfig = {
specStatus: "ED",
shortName: "api-design",
editors: [
{ name: "Robin Berjon",
url: "http://berjon.com/",
company: "Robineko",
companyURL: "http://robineko.com/" }
],
edDraftURI: "http://scriptlib-cg.github.com/api-design-cookbook/",
copyrightStart: 2011,
noIDLIn: true,
// wg: "Web Applications (WebApps) Working Group",
// wgURI: "http://www.w3.org/2008/webapps/",
// wgPublicList: "public-webapps",
// wgPatentURI: "http://www.w3.org/2004/01/pp-impl/42538/status",
wg: "Device APIs Working Group",
wgURI: "http://www.w3.org/2009/dap/",
wgPublicList: "public-device-apis",
wgPatentURI: "http://www.w3.org/2004/01/pp-impl/43696/status",
};
</script>
<script src='http://respec.specifiction.com/js/profiles/w3c-common.js' class='remove'></script>
</head>
<body>
<section id='abstract'>
<p>
This document captures common practices in designing APIs that fit well into the Web platform as
a whole, using WebIDL [[!WEBIDL]].
</p>
</section>
<section id='sotd'>
<p>
As it currently stands, this document is nothing more than a proposal from its editor, with no
backing implied or otherwise from any other party.
</p>
</section>
<section>
<h2>Introduction</h2>
<p>
Over a relatively short period of time the number of different APIs being created for use on the
Web has grown at a sustained pace. In working on these interfaces, many in the community discuss
the benefits of certain approaches over others and reach agreement as to the preferred solution when
facing a given problem.
</p>
<p>
Keeping track of all these gems is however difficult given the volume of work being carried on
in parallel and the sometimes disjointed nature of the groups involved. As a result, it can take
a long while and many arguments repeated almost identically before a good solution becomes common.
</p>
<p>
What's more, it is altogether too rare to garner feedback from developers who are actually using
these features in their day-to-day work. Because of this, several APIs have shipped with issues
that could have been caught earlier on in the process. This document therefore also endeavours
to elicit feedback from developers in order to make it more readily available to API designers.
</p>
<p>
The goal of this document is to capture such ideas as they appear and accrete them over time so as
to constitute a repository of knowledge on this topic. As a guide it does not however attempt to
supplant editors' brains in making decisions as to how to design their APIs, and consequently one
must keep in mind that the reasoning behind a specific recommendation is often more important than
its result. Furthermore, in some cases there may not be a single consensual best approach, and
editors will need to understand the tradeoffs involved in order to pick what works for them amongst
a set of options.
</p>
</section>
<section>
<h2>Using Dictionaries</h2>
<p>
...
</p>
</section>
<section>
<h2>Ensuring that Feature Detection is Possible</h2>
<p>
...
</p>
</section>
<section>
<h2>When To Be Asynchronous</h2>
<p>
...
</p>
</section>
<section>
<h2>Specifying Callbacks</h2>
<p>
Asynchronous method calls require a point of reentry in the form of a callback. There are multiple
ways of specifying these, none of which is consensually consider the one and only approach.
</p>
<p>
One is <dfn>success/error callback functions</dfn>, as used in [[GEOLOCATION-API]]. It takes the shape of a method
call that accepts a callback function for success followed by one for errors. There may be arguments
before the success callback, and the error callback is usually optional.
</p>
<pre class='example highlight'>
navigator.petKitten(
function () {
console.log("kitten is purring");
}
, function (err) {
console.log("kitten hates you, because: " + err);
}
);
</pre>
<p>
Downsides of this approach are that it is a little more verbose and does not encourage error handling. It
also lacks in extensibility since adding new arguments to the method will typically require placing them
after the optional error handler — making them de facto optional. Likewise, extending it to support not
just callbacks for success and error but also for other situations that may warrant reporting information
back (e.g. progress being made) is clumsy at best. Since it is only used in [[GEOLOCATION-API]] it does not
appear to be a required pattern for consistency.
</p>
<p>Another approach is to define new DOM Events that are called when an underlying system changes.</p>
<pre class='example higlight'>
window.addEventListener('kittenpurr',
function () {
console.log("kitten is purring");
}, false);
</pre>
<p>On one hand, most developers are quite familiar with DOM Events, and they make it easy to have several handlers for a given event.</p>
<p>On the other hand, using DOM Events for listening to systems that need specific activation (e.g. powering up) are usually not a good idea: <code>addEventListener</code> is supposed to be free from side effects, and associating activation code with it breaks that promise. The [[DeviceOrientation]] specification is as such an <strong>anti-pattern</strong> that should not be replicated. See <a href="http://lists.w3.org/Archives/Public/public-device-apis/2011Nov/0164.html">some discussions on side effects in addEventListener</a>.</p>
<p>
Another is <dfn>returning an EventTarget object</dfn>, as pioneered by [[XMLHTTPREQUEST]]. This consists
in opening a request that returns an object on which event handlers can be registered, then starting
that request with a specific call.
</p>
<pre class='example highlight'>
var kitpet = navigator.openKittenPetting();
kitpet.onpurr = function () { console.log("kitten is purring"); };
kitpet.ondrool = function () { console.log("kitten is now drooling"); };
kitpet.onviciousbite = function () { console.log("kitten bit your hand off"); };
kitpet.start();
</pre>
<p>
The approach is the most verbose and does not encourage error handling, but it does have the advantages
of being trivially extended for greater feedback, and of being familiar to users of [[XMLHTTPREQUEST]]
(and now [[INDEXEDDB]]). It can, however, feel rather overkill if extensions for additional events does
not seem likely.
</p>
<p>
Then there are <dfn>NodeJS style callbacks</dfn>. For every asynchronous operation, every callback takes
as first parameter an error object which is <code>null</code> when the operation was a success, followed
by as many pieces of information as are needed to capture the success.
</p>
<pre class='example highlight'>
navigator.petKitten(function (err, state) {
if (err) console.log("kitten mauled your face with extreme prejudice");
else console.log("kittem manifest joy by " + state);
})
</pre>
<p>
The primary value in this approach is that it puts errors "right there" where they are likely to be handled.
Its consistency is also very helpful in creating chains of callbacks, a feature that proves precious
when multiple callbacks can become nested. The major downside of this approach is that it is currently
not used in the rest of stack and therefore probably only familiar to NodeJS developers (which granted
represent a fast-growing proportion of the general web developer population).
</p>
<p>
Finally there are <dfn>promises</dfn>. These are similar to <a>returning an EventTarget object</a> but
more generic and use a slightly different style.
</p>
<pre class='example highlight'>
navigator.petKitten()
.then(function () { console.log("kitten is purring"); })
.fail(function () { console.log("kitten bit your hand off"); })
.run();
</pre>
<p>
There is elegance and flexibility in promises, but to date they are used in no Web specification.
</p>
</section>
<section>
<h2>Exceptions</h2>
<p>
...
</p>
</section>
<section>
<h2>Don't Use Numerical Constants</h2>
<p>
Interfaces regularly require constant identifiers. You have a node and you want to know if it's
an element or a comment. You have a message and you want to know if it's email or SMS. That much makes
sense. What does not make sense is insisting on naming these things with numbers when we could name them
with, you know, names.
</p>
<p>
There are several variants in this bad practice. First, is the lovingly terse approach:
</p>
<pre class='example highlight'>
if (foo.type === 17) doSomething();
</pre>
<p>
Then there is the comment you have to paste every time:
</p>
<pre class='example highlight'>
// check if this of the fungible type
if (foo.type === 17) doSomething();
</pre>
<p>
Or we can have the uselessly long variant:
</p>
<pre class='example highlight'>
if (foo.type === FooInterface.FUNGIBLE_TYPE) doSomething();
</pre>
<p>
And there's the meh option:
</p>
<pre class='example highlight'>
var isFungible = FooInterface.FUNGIBLE_TYPE;
// ...
if (foo.type === isFungible) doSomething();
</pre>
<p>
None of the above is as simple, terse, and self-documenting as:
</p>
<pre class='example highlight'>
if (foo.type === "fungible") doSomething();
</pre>
<p>
We're not in the 1970s, standardising UNIX system calls. Strings are lightweight enough,
and readable. There is no need to define string constants to support these: just the plain
strings, defined as such in specification prose, work. In some languages, this can lose
static verification, but in JavaScript it makes no difference: if you misspell the the
string you might have equally misspelt the constant, with the same failure.
</p>
<p>
Additionally, interfaces that don't start out by listing a dozen (useless) constants are
more readable. The only downside to doing away with numerical constants is the negative
impact it may on occasion have on feature detection. It would however be better to rely
on other aspects that enable feature detection, since these constants can easily be added
for a half-baked feature (and frequently have been).
</p>
</section>
<section>
<h2>When to use <code>NoInterfaceObject</code></h2>
<p>
...
</p>
</section>
<section>
<h2>WebIDL Legacy Features</h2>
<p>
The features of WebIDL listed in this section have been specified for the sole purpose of describing
legacy APIs such as are found in “DOM 0”. It is particularly important that you do not use them unless
you are trying to document one such API — they must not be used for any kind of new work. The list
includes:
</p>
<p class='issue'>
Get an actual list.
</p>
</section>
<section class='appendix'>
<h2>Acknowledgements</h2>
<p>
Many thanks to Cameron McCormack, Marcos Càceres, and Andreas Gal for their input.
</p>
</section>
</body>
</html>