|
525
|
1 |
<!DOCTYPE html> |
|
|
2 |
<html lang="en"> |
|
|
3 |
<head> |
|
|
4 |
<meta charset="utf-8"> |
|
|
5 |
<title>Example: Wrapping async transactions with promises</title> |
|
|
6 |
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=PT+Sans:400,700,400italic,700italic"> |
|
|
7 |
<link rel="stylesheet" href="../../build/cssgrids/cssgrids-min.css"> |
|
|
8 |
<link rel="stylesheet" href="../assets/css/main.css"> |
|
|
9 |
<link rel="stylesheet" href="../assets/vendor/prettify/prettify-min.css"> |
|
|
10 |
<link rel="shortcut icon" type="image/png" href="../assets/favicon.png"> |
|
|
11 |
<script src="../../build/yui/yui-min.js"></script> |
|
|
12 |
|
|
|
13 |
</head> |
|
|
14 |
<body> |
|
|
15 |
<!-- |
|
|
16 |
<a href="https://github.com/yui/yui3"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a> |
|
|
17 |
--> |
|
|
18 |
<div id="doc"> |
|
|
19 |
<div id="hd"> |
|
|
20 |
<h1><img src="http://yuilibrary.com/img/yui-logo.png"></h1> |
|
|
21 |
</div> |
|
|
22 |
|
|
|
23 |
<a href="#toc" class="jump">Jump to Table of Contents</a> |
|
|
24 |
|
|
|
25 |
|
|
|
26 |
<h1>Example: Wrapping async transactions with promises</h1> |
|
|
27 |
<div class="yui3-g"> |
|
|
28 |
<div class="yui3-u-3-4"> |
|
|
29 |
<div id="main"> |
|
|
30 |
<div class="content"><style scoped> |
|
|
31 |
#demo div { |
|
|
32 |
padding: 5px; |
|
|
33 |
margin: 2px; |
|
|
34 |
} |
|
|
35 |
.success { |
|
|
36 |
background: #BBE599; |
|
|
37 |
} |
|
|
38 |
.error { |
|
|
39 |
background: #ffc5c4; |
|
|
40 |
} |
|
|
41 |
</style> |
|
|
42 |
|
|
|
43 |
|
|
|
44 |
<div class="intro"> |
|
|
45 |
<p> |
|
|
46 |
This example shows how to create a cache for the GitHub Contributors API that returns promises representing the values you fetched. In order to access the API we use the JSONP module. |
|
|
47 |
</p> |
|
|
48 |
</div> |
|
|
49 |
|
|
|
50 |
<div class="example yui3-skin-sam"> |
|
|
51 |
<div id="demo"></div> |
|
|
52 |
<script> |
|
|
53 |
YUI().use('node', 'jsonp', 'promise', 'escape', function (Y) { |
|
|
54 |
|
|
|
55 |
// A cache for GitHub user data |
|
|
56 |
var GitHub = (function () { |
|
|
57 |
|
|
|
58 |
var cache = {}, |
|
|
59 |
githubURL = 'https://api.github.com/users/{user}?callback={callback}'; |
|
|
60 |
|
|
|
61 |
function getUserURL(name) { |
|
|
62 |
return Y.Lang.sub(githubURL, { |
|
|
63 |
user: name |
|
|
64 |
}); |
|
|
65 |
} |
|
|
66 |
|
|
|
67 |
// Fetches a URL, stores a promise in the cache and returns it |
|
|
68 |
function fetch(url) { |
|
|
69 |
var promise = new Y.Promise(function (fulfill, reject) { |
|
|
70 |
Y.jsonp(url, function (res) { |
|
|
71 |
var meta = res.meta, |
|
|
72 |
data = res.data; |
|
|
73 |
|
|
|
74 |
// Check for a successful response, otherwise reject the |
|
|
75 |
// promise with the message returned by the GitHub API. |
|
|
76 |
if (meta.status >= 200 && meta.status < 300) { |
|
|
77 |
fulfill(data); |
|
|
78 |
} else { |
|
|
79 |
reject(new Error(data.message)); |
|
|
80 |
} |
|
|
81 |
}); |
|
|
82 |
|
|
|
83 |
// Add a timeout in case the URL is completely wrong |
|
|
84 |
// or GitHub is too busy |
|
|
85 |
setTimeout(function () { |
|
|
86 |
// Once a promise has been fulfilled or rejected it will never |
|
|
87 |
// change its state again, so we can safely call reject() after |
|
|
88 |
// some time. If it was already fulfilled or rejected, nothing will |
|
|
89 |
// happen |
|
|
90 |
reject(new Error('Timeout')); |
|
|
91 |
}, 10000); |
|
|
92 |
}); |
|
|
93 |
|
|
|
94 |
// store the promise in the cache object |
|
|
95 |
cache[url] = promise; |
|
|
96 |
|
|
|
97 |
return promise; |
|
|
98 |
} |
|
|
99 |
|
|
|
100 |
return { |
|
|
101 |
getUser: function (name) { |
|
|
102 |
var url = getUserURL(name); |
|
|
103 |
|
|
|
104 |
if (cache[url]) { |
|
|
105 |
// If we have already stored the promise in the cache we just return it |
|
|
106 |
return cache[url]; |
|
|
107 |
} else { |
|
|
108 |
// fetch() will make a JSONP request, cache the promise and return it |
|
|
109 |
return fetch(url); |
|
|
110 |
} |
|
|
111 |
} |
|
|
112 |
}; |
|
|
113 |
}()); |
|
|
114 |
|
|
|
115 |
|
|
|
116 |
var demo = Y.one('#demo'), |
|
|
117 |
SUCCESS_TEMPLATE = '<div class="success">Loaded {name}</a>\'s data! ' + |
|
|
118 |
'<a href="{link}">Link to profile</a></div>', |
|
|
119 |
FAILURE_TEMPLATE = '<div class="error">{message}</div>'; |
|
|
120 |
|
|
|
121 |
function renderUser(user) { |
|
|
122 |
demo.append(Y.Lang.sub(SUCCESS_TEMPLATE, { |
|
|
123 |
// escape the values gotten from the GitHub API to avoid unexpected |
|
|
124 |
// HTML injection which could be an XSS vulnerability |
|
|
125 |
name: Y.Escape.html(user.login), |
|
|
126 |
link: Y.Escape.html(user.html_url) |
|
|
127 |
})); |
|
|
128 |
} |
|
|
129 |
function showError(err) { |
|
|
130 |
demo.append(Y.Lang.sub(FAILURE_TEMPLATE, { |
|
|
131 |
message: Y.Escape.html(err.message) |
|
|
132 |
})); |
|
|
133 |
} |
|
|
134 |
|
|
|
135 |
GitHub.getUser('yui').then(renderUser, showError); |
|
|
136 |
GitHub.getUser('y u i').then(renderUser, showError); |
|
|
137 |
|
|
|
138 |
}); |
|
|
139 |
</script> |
|
|
140 |
|
|
|
141 |
</div> |
|
|
142 |
|
|
|
143 |
<h3 id="creating-a-cache">Creating a Cache</h3> |
|
|
144 |
|
|
|
145 |
<p> |
|
|
146 |
A cache is an object that keeps track of which operations have already been performed, stores the results and returns the stored result if the operation was already performed. In this case, since we are fetching content with JSONP, the operations are asynchronous so we will store promises representing them. |
|
|
147 |
</p> |
|
|
148 |
|
|
|
149 |
<pre class="code prettyprint">// We create a simple module with a private cache object |
|
|
150 |
var GitHub = (function () { |
|
|
151 |
|
|
|
152 |
var cache = {}; |
|
|
153 |
|
|
|
154 |
return { |
|
|
155 |
getUser: function (name) { |
|
|
156 |
// This method will return a promise |
|
|
157 |
} |
|
|
158 |
}; |
|
|
159 |
}());</pre> |
|
|
160 |
|
|
|
161 |
|
|
|
162 |
<p> |
|
|
163 |
Given a certain function that takes a user name and returns the corresponding GitHub API URL, then a method that caches the user data will simply check the private cache object or fetch the result. |
|
|
164 |
</p> |
|
|
165 |
|
|
|
166 |
<pre class="code prettyprint">getUser: function (name) { |
|
|
167 |
var url = getUserURL(name); |
|
|
168 |
|
|
|
169 |
if (cache[url]) { |
|
|
170 |
// If we have already stored the promise in the cache we just return it |
|
|
171 |
return cache[url]; |
|
|
172 |
} else { |
|
|
173 |
// fetch() will make a JSONP request, cache the promise and return it |
|
|
174 |
return fetch(url); |
|
|
175 |
} |
|
|
176 |
}</pre> |
|
|
177 |
|
|
|
178 |
|
|
|
179 |
<h3 id="resolving-and-returning-promises">Resolving and Returning Promises</h3> |
|
|
180 |
|
|
|
181 |
<p>Our <code>fetch()</code> function will create a promise and fulfill it or reject it based on the result of the JSONP request. Following the steps described in the <a href="index.html##creating-a-promise">User Guide</a>, we create a promise and call <code>Y.jsonp</code> inside its initialization function.</p> |
|
|
182 |
|
|
|
183 |
<pre class="code prettyprint">// Fetches a URL, stores a promise in the cache and returns it |
|
|
184 |
function fetch(url) { |
|
|
185 |
var promise = new Y.Promise(function (fulfill, reject) { |
|
|
186 |
Y.jsonp(url, function (res) { |
|
|
187 |
var meta = res.meta, |
|
|
188 |
data = res.data; |
|
|
189 |
|
|
|
190 |
// Check for a successful response, otherwise reject the |
|
|
191 |
// promise with the message returned by the GitHub API. |
|
|
192 |
if (meta.status >= 200 && meta.status < 300) { |
|
|
193 |
fulfill(data); |
|
|
194 |
} else { |
|
|
195 |
reject(new Error(data.message)); |
|
|
196 |
} |
|
|
197 |
}); |
|
|
198 |
|
|
|
199 |
// Add a timeout in case the URL is completely wrong |
|
|
200 |
// or GitHub is too busy |
|
|
201 |
setTimeout(function () { |
|
|
202 |
// Once a promise has been fulfilled or rejected it will never |
|
|
203 |
// change its state again, so we can safely call reject() after |
|
|
204 |
// some time. If it was already fulfilled or rejected, nothing will |
|
|
205 |
// happen |
|
|
206 |
reject(new Error('Timeout')); |
|
|
207 |
}, 10000); |
|
|
208 |
}); |
|
|
209 |
|
|
|
210 |
// store the promise in the cache object |
|
|
211 |
cache[url] = promise; |
|
|
212 |
|
|
|
213 |
return promise; |
|
|
214 |
}</pre> |
|
|
215 |
|
|
|
216 |
|
|
|
217 |
<h3 id="wiring-it-all-together">Wiring It All Together</h3> |
|
|
218 |
|
|
|
219 |
<p>Here is the complete code for this example. You will notice that it contains a request for a user called "y u i" which likely does not exist. This illustrates how promises help you handle errors. While it may be tempting to skip adding an error callback, it is highly recommended that you add one and provide feedback to your users when things go wrong.</p> |
|
|
220 |
<h4 id="html">HTML</h4> |
|
|
221 |
<pre class="code prettyprint"><div id="demo"></div></pre> |
|
|
222 |
|
|
|
223 |
|
|
|
224 |
<h4 id="css">CSS</h4> |
|
|
225 |
<pre class="code prettyprint"><style scoped> |
|
|
226 |
#demo div { |
|
|
227 |
padding: 5px; |
|
|
228 |
margin: 2px; |
|
|
229 |
} |
|
|
230 |
.success { |
|
|
231 |
background: #BBE599; |
|
|
232 |
} |
|
|
233 |
.error { |
|
|
234 |
background: #ffc5c4; |
|
|
235 |
} |
|
|
236 |
</style></pre> |
|
|
237 |
|
|
|
238 |
|
|
|
239 |
<h4 id="javascript">JavaScript</h4> |
|
|
240 |
<pre class="code prettyprint"><script> |
|
|
241 |
YUI().use('node', 'jsonp', 'promise', 'escape', function (Y) { |
|
|
242 |
|
|
|
243 |
// A cache for GitHub user data |
|
|
244 |
var GitHub = (function () { |
|
|
245 |
|
|
|
246 |
var cache = {}, |
|
|
247 |
githubURL = 'https://api.github.com/users/{user}?callback={callback}'; |
|
|
248 |
|
|
|
249 |
function getUserURL(name) { |
|
|
250 |
return Y.Lang.sub(githubURL, { |
|
|
251 |
user: name |
|
|
252 |
}); |
|
|
253 |
} |
|
|
254 |
|
|
|
255 |
// Fetches a URL, stores a promise in the cache and returns it |
|
|
256 |
function fetch(url) { |
|
|
257 |
var promise = new Y.Promise(function (fulfill, reject) { |
|
|
258 |
Y.jsonp(url, function (res) { |
|
|
259 |
var meta = res.meta, |
|
|
260 |
data = res.data; |
|
|
261 |
|
|
|
262 |
// Check for a successful response, otherwise reject the |
|
|
263 |
// promise with the message returned by the GitHub API. |
|
|
264 |
if (meta.status >= 200 && meta.status < 300) { |
|
|
265 |
fulfill(data); |
|
|
266 |
} else { |
|
|
267 |
reject(new Error(data.message)); |
|
|
268 |
} |
|
|
269 |
}); |
|
|
270 |
|
|
|
271 |
// Add a timeout in case the URL is completely wrong |
|
|
272 |
// or GitHub is too busy |
|
|
273 |
setTimeout(function () { |
|
|
274 |
// Once a promise has been fulfilled or rejected it will never |
|
|
275 |
// change its state again, so we can safely call reject() after |
|
|
276 |
// some time. If it was already fulfilled or rejected, nothing will |
|
|
277 |
// happen |
|
|
278 |
reject(new Error('Timeout')); |
|
|
279 |
}, 10000); |
|
|
280 |
}); |
|
|
281 |
|
|
|
282 |
// store the promise in the cache object |
|
|
283 |
cache[url] = promise; |
|
|
284 |
|
|
|
285 |
return promise; |
|
|
286 |
} |
|
|
287 |
|
|
|
288 |
return { |
|
|
289 |
getUser: function (name) { |
|
|
290 |
var url = getUserURL(name); |
|
|
291 |
|
|
|
292 |
if (cache[url]) { |
|
|
293 |
// If we have already stored the promise in the cache we just return it |
|
|
294 |
return cache[url]; |
|
|
295 |
} else { |
|
|
296 |
// fetch() will make a JSONP request, cache the promise and return it |
|
|
297 |
return fetch(url); |
|
|
298 |
} |
|
|
299 |
} |
|
|
300 |
}; |
|
|
301 |
}()); |
|
|
302 |
|
|
|
303 |
|
|
|
304 |
var demo = Y.one('#demo'), |
|
|
305 |
SUCCESS_TEMPLATE = '<div class="success">Loaded {name}</a>\'s data! ' + |
|
|
306 |
'<a href="{link}">Link to profile</a></div>', |
|
|
307 |
FAILURE_TEMPLATE = '<div class="error">{message}</div>'; |
|
|
308 |
|
|
|
309 |
function renderUser(user) { |
|
|
310 |
demo.append(Y.Lang.sub(SUCCESS_TEMPLATE, { |
|
|
311 |
// escape the values gotten from the GitHub API to avoid unexpected |
|
|
312 |
// HTML injection which could be an XSS vulnerability |
|
|
313 |
name: Y.Escape.html(user.login), |
|
|
314 |
link: Y.Escape.html(user.html_url) |
|
|
315 |
})); |
|
|
316 |
} |
|
|
317 |
function showError(err) { |
|
|
318 |
demo.append(Y.Lang.sub(FAILURE_TEMPLATE, { |
|
|
319 |
message: Y.Escape.html(err.message) |
|
|
320 |
})); |
|
|
321 |
} |
|
|
322 |
|
|
|
323 |
GitHub.getUser('yui').then(renderUser, showError); |
|
|
324 |
GitHub.getUser('y u i').then(renderUser, showError); |
|
|
325 |
|
|
|
326 |
}); |
|
|
327 |
</script></pre> |
|
|
328 |
|
|
|
329 |
</div> |
|
|
330 |
</div> |
|
|
331 |
</div> |
|
|
332 |
|
|
|
333 |
<div class="yui3-u-1-4"> |
|
|
334 |
<div class="sidebar"> |
|
|
335 |
|
|
|
336 |
<div id="toc" class="sidebox"> |
|
|
337 |
<div class="hd"> |
|
|
338 |
<h2 class="no-toc">Table of Contents</h2> |
|
|
339 |
</div> |
|
|
340 |
|
|
|
341 |
<div class="bd"> |
|
|
342 |
<ul class="toc"> |
|
|
343 |
<li> |
|
|
344 |
<a href="#creating-a-cache">Creating a Cache</a> |
|
|
345 |
</li> |
|
|
346 |
<li> |
|
|
347 |
<a href="#resolving-and-returning-promises">Resolving and Returning Promises</a> |
|
|
348 |
</li> |
|
|
349 |
<li> |
|
|
350 |
<a href="#wiring-it-all-together">Wiring It All Together</a> |
|
|
351 |
<ul class="toc"> |
|
|
352 |
<li> |
|
|
353 |
<a href="#html">HTML</a> |
|
|
354 |
</li> |
|
|
355 |
<li> |
|
|
356 |
<a href="#css">CSS</a> |
|
|
357 |
</li> |
|
|
358 |
<li> |
|
|
359 |
<a href="#javascript">JavaScript</a> |
|
|
360 |
</li> |
|
|
361 |
</ul> |
|
|
362 |
</li> |
|
|
363 |
</ul> |
|
|
364 |
</div> |
|
|
365 |
</div> |
|
|
366 |
|
|
|
367 |
|
|
|
368 |
|
|
|
369 |
<div class="sidebox"> |
|
|
370 |
<div class="hd"> |
|
|
371 |
<h2 class="no-toc">Examples</h2> |
|
|
372 |
</div> |
|
|
373 |
|
|
|
374 |
<div class="bd"> |
|
|
375 |
<ul class="examples"> |
|
|
376 |
|
|
|
377 |
|
|
|
378 |
<li data-description="Wrapping async transactions with promises"> |
|
|
379 |
<a href="basic-example.html">Wrapping async transactions with promises</a> |
|
|
380 |
</li> |
|
|
381 |
|
|
|
382 |
|
|
|
383 |
|
|
|
384 |
<li data-description="Extend Y.Promise to create classes that encapsulate standard transaction logic in descriptive method names"> |
|
|
385 |
<a href="subclass-example.html">Subclassing Y.Promise</a> |
|
|
386 |
</li> |
|
|
387 |
|
|
|
388 |
|
|
|
389 |
|
|
|
390 |
<li data-description="Extend the Promise class to create your own Node plugin that chains transitions"> |
|
|
391 |
<a href="plugin-example.html">Creating a Node Plugin that chains transitions</a> |
|
|
392 |
</li> |
|
|
393 |
|
|
|
394 |
|
|
|
395 |
</ul> |
|
|
396 |
</div> |
|
|
397 |
</div> |
|
|
398 |
|
|
|
399 |
|
|
|
400 |
|
|
|
401 |
</div> |
|
|
402 |
</div> |
|
|
403 |
</div> |
|
|
404 |
</div> |
|
|
405 |
|
|
|
406 |
<script src="../assets/vendor/prettify/prettify-min.js"></script> |
|
|
407 |
<script>prettyPrint();</script> |
|
|
408 |
|
|
|
409 |
<script> |
|
|
410 |
YUI.Env.Tests = { |
|
|
411 |
examples: [], |
|
|
412 |
project: '../assets', |
|
|
413 |
assets: '../assets/promise', |
|
|
414 |
name: 'basic-example', |
|
|
415 |
title: 'Wrapping async transactions with promises', |
|
|
416 |
newWindow: '', |
|
|
417 |
auto: false |
|
|
418 |
}; |
|
|
419 |
YUI.Env.Tests.examples.push('basic-example'); |
|
|
420 |
YUI.Env.Tests.examples.push('subclass-example'); |
|
|
421 |
YUI.Env.Tests.examples.push('plugin-example'); |
|
|
422 |
|
|
|
423 |
</script> |
|
|
424 |
<script src="../assets/yui/test-runner.js"></script> |
|
|
425 |
|
|
|
426 |
|
|
|
427 |
|
|
|
428 |
</body> |
|
|
429 |
</html> |