|
1 <!DOCTYPE html> |
|
2 <html lang="en"> |
|
3 <head> |
|
4 <meta charset="utf-8"> |
|
5 <title>Example: Subclassing Y.Promise</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: Subclassing Y.Promise</h1> |
|
27 <div class="yui3-g"> |
|
28 <div class="yui3-u-3-4"> |
|
29 <div id="main"> |
|
30 <div class="content"><style scoped> |
|
31 .error { |
|
32 background: #ffc5c4; |
|
33 } |
|
34 </style> |
|
35 |
|
36 |
|
37 <div class="intro"> |
|
38 <p> |
|
39 This example expands on the <a href="basic-example.html">Wrapping async transactions with promises</a> example to illustrate how to create your own Promise subclass for performing operations on arrays. |
|
40 </p> |
|
41 </div> |
|
42 |
|
43 <div class="example yui3-skin-sam"> |
|
44 <div id="demo"></div> |
|
45 <script> |
|
46 YUI().use('promise', 'jsonp', 'node', 'array-extras', function (Y) { |
|
47 |
|
48 function ArrayPromise() { |
|
49 ArrayPromise.superclass.constructor.apply(this, arguments); |
|
50 } |
|
51 Y.extend(ArrayPromise, Y.Promise); |
|
52 |
|
53 // Although Y.Array.each does not return an array, for the purpose of this |
|
54 // example we make it chainable by returning the same array |
|
55 ArrayPromise.prototype.each = function (fn, thisObj) { |
|
56 return this.then(function (array) { |
|
57 Y.Array.each(array, fn, thisObj); |
|
58 return array; |
|
59 }); |
|
60 }; |
|
61 |
|
62 // Y.Array.map returns a new array, so we return the result of this.then() |
|
63 ArrayPromise.prototype.map = function (fn, thisObj) { |
|
64 return this.then(function (array) { |
|
65 // By returning the result of Y.Array.map we are returning a new promise |
|
66 // representing the new array |
|
67 return Y.Array.map(array, fn, thisObj); |
|
68 }); |
|
69 }; |
|
70 |
|
71 // Y.Array.filter follows the same pattern as Y.Array.map |
|
72 ArrayPromise.prototype.filter = function (fn, thisObj) { |
|
73 return this.then(function (array) { |
|
74 return Y.Array.filter(array, fn, thisObj); |
|
75 }); |
|
76 }; |
|
77 |
|
78 // Takes any promise and returns an ArrayPromise |
|
79 function toArrayPromise(promise) { |
|
80 return new ArrayPromise(function (fulfill, reject) { |
|
81 promise.then(fulfill, reject); |
|
82 }); |
|
83 } |
|
84 |
|
85 |
|
86 // A cache for GitHub user data |
|
87 var GitHub = (function () { |
|
88 |
|
89 var cache = {}, |
|
90 githubURL = 'https://api.github.com/users/{user}?callback={callback}'; |
|
91 |
|
92 function getUserURL(name) { |
|
93 return Y.Lang.sub(githubURL, { |
|
94 user: name |
|
95 }); |
|
96 } |
|
97 |
|
98 // Fetches a URL, stores a promise in the cache and returns it |
|
99 function fetch(url) { |
|
100 var promise = new Y.Promise(function (fulfill, reject) { |
|
101 Y.jsonp(url, function (res) { |
|
102 var meta = res.meta, |
|
103 data = res.data; |
|
104 |
|
105 // Check for a successful response, otherwise reject the |
|
106 // promise with the message returned by the GitHub API. |
|
107 if (meta.status >= 200 && meta.status < 300) { |
|
108 fulfill(data); |
|
109 } else { |
|
110 reject(new Error(data.message)); |
|
111 } |
|
112 }); |
|
113 |
|
114 // Add a timeout in case the URL is completely wrong |
|
115 // or GitHub is too busy |
|
116 setTimeout(function () { |
|
117 // Once a promise has been fulfilled or rejected it will never |
|
118 // change its state again, so we can safely call reject() after |
|
119 // some time. If it was already fulfilled or rejected, nothing will |
|
120 // happen |
|
121 reject(new Error('Timeout')); |
|
122 }, 10000); |
|
123 }); |
|
124 |
|
125 // store the promise in the cache object |
|
126 cache[url] = promise; |
|
127 |
|
128 return promise; |
|
129 } |
|
130 |
|
131 return { |
|
132 getUser: function (name) { |
|
133 var url = getUserURL(name); |
|
134 |
|
135 if (cache[url]) { |
|
136 // If we have already stored the promise in the cache we just return it |
|
137 return cache[url]; |
|
138 } else { |
|
139 // fetch() will make a JSONP request, cache the promise and return it |
|
140 return fetch(url); |
|
141 } |
|
142 } |
|
143 }; |
|
144 }()); |
|
145 |
|
146 |
|
147 var demoNode = Y.one('#demo'); |
|
148 |
|
149 function log(text) { |
|
150 demoNode.append(Y.Node.create('<div></div>').set('text', text)); |
|
151 } |
|
152 function showError(message) { |
|
153 demoNode.append( |
|
154 Y.Node.create('<div class="error"></div>').set('text', message) |
|
155 ); |
|
156 } |
|
157 |
|
158 log('Fetching GitHub data for users: "yui", "yahoo" and "davglass"...') |
|
159 |
|
160 // requests is a regular promise |
|
161 var requests = Y.batch(GitHub.getUser('yui'), GitHub.getUser('yahoo'), GitHub.getUser('davglass')); |
|
162 // users is now an ArrayPromise |
|
163 var users = toArrayPromise(requests); |
|
164 |
|
165 // Transform the data into a list of names |
|
166 users.map(function (data) { |
|
167 log('Getting name for user "' + data.login + '"...') |
|
168 return data.name; |
|
169 }).filter(function (name) { |
|
170 log('Checking if the name "' + name + '" starts with "Y"...') |
|
171 return name.charAt(0) === 'Y'; |
|
172 }).then(function (names) { |
|
173 log('Done!'); |
|
174 return names; |
|
175 }).each(function (name, i) { |
|
176 log(i + '. ' + name); |
|
177 }).then(null, function (error) { |
|
178 // if there was an error in any step or request, it is automatically |
|
179 // passed around the promise chain so we can react to it at the end |
|
180 showError(error.message); |
|
181 }); |
|
182 |
|
183 }); |
|
184 </script> |
|
185 |
|
186 </div> |
|
187 |
|
188 <h2 id="subclassing-ypromise">Subclassing Y.Promise</h2> |
|
189 |
|
190 <p> |
|
191 You can subclass a YUI promise with <a href="../yui/yui-extend.html">Y.extend</a> the same way you would any other class. Keep in mind that Promise constructors take a function as a parameter so you need to call the superclass constructor in order for it to work. |
|
192 </p> |
|
193 |
|
194 <pre class="code prettyprint">function ArrayPromise() { |
|
195 ArrayPromise.superclass.constructor.apply(this, arguments); |
|
196 } |
|
197 Y.extend(ArrayPromise, Y.Promise);</pre> |
|
198 |
|
199 |
|
200 <h2 id="method-chaining">Method Chaining</h2> |
|
201 |
|
202 <p> |
|
203 Chaining promise methods is done by returning the result of calling the promise's <code>then()</code> method. <code>then()</code> <strong>always returns a promise of its same kind</strong>, so this will allow us to chain array operations as if they were real arrays. |
|
204 </p> |
|
205 <p> |
|
206 For the purpose of this example we will only add the <code>each</code>, <code>filter</code> and <code>map</code> methods from the <code>array-extras</code> module. |
|
207 </p> |
|
208 |
|
209 <pre class="code prettyprint">// Although Y.Array.each does not return an array, for the purpose of this |
|
210 // example we make it chainable by returning the same array |
|
211 ArrayPromise.prototype.each = function (fn, thisObj) { |
|
212 return this.then(function (array) { |
|
213 Y.Array.each(array, fn, thisObj); |
|
214 return array; |
|
215 }); |
|
216 }; |
|
217 |
|
218 // Y.Array.map returns a new array, so we return the result of this.then() |
|
219 ArrayPromise.prototype.map = function (fn, thisObj) { |
|
220 return this.then(function (array) { |
|
221 // By returning the result of Y.Array.map we are returning a new promise |
|
222 // representing the new array |
|
223 return Y.Array.map(array, fn, thisObj); |
|
224 }); |
|
225 }; |
|
226 |
|
227 // Y.Array.filter follows the same pattern as Y.Array.map |
|
228 ArrayPromise.prototype.filter = function (fn, thisObj) { |
|
229 return this.then(function (array) { |
|
230 return Y.Array.filter(array, fn, thisObj); |
|
231 }); |
|
232 };</pre> |
|
233 |
|
234 |
|
235 <p> |
|
236 Finally we need a simple way to take a promise that we know contains an array and create an ArrayPromise with its value. |
|
237 </p> |
|
238 |
|
239 <pre class="code prettyprint">// Takes any promise and returns an ArrayPromise |
|
240 function toArrayPromise(promise) { |
|
241 return new ArrayPromise(function (fulfill, reject) { |
|
242 promise.then(fulfill, reject); |
|
243 }); |
|
244 }</pre> |
|
245 |
|
246 |
|
247 <h3 id="putting-our-class-to-action">Putting our Class to Action</h3> |
|
248 |
|
249 <p> |
|
250 There are many cases in which you would want to work on asynchronous array values. Performing more than one async operation at a time and dealing with the result is one common use case. <code>Y.batch</code> waits for many operations and returns a promise representing an array with the result of all the operations, so you could wrap it in an ArrayPromise to modify all those results. |
|
251 </p> |
|
252 |
|
253 <p> |
|
254 We will use the JSONP Cache from <a href="jsonp-cache.html">the previous example</a> and make several simultaneous requests. |
|
255 </p> |
|
256 |
|
257 <pre class="code prettyprint">log('Fetching GitHub data for users: "yui", "yahoo" and "davglass"...') |
|
258 |
|
259 // requests is a regular promise |
|
260 var requests = Y.batch(GitHub.getUser('yui'), GitHub.getUser('yahoo'), GitHub.getUser('davglass')); |
|
261 // users is now an ArrayPromise |
|
262 var users = toArrayPromise(requests); |
|
263 |
|
264 // Transform the data into a list of names |
|
265 users.map(function (data) { |
|
266 log('Getting name for user "' + data.login + '"...') |
|
267 return data.name; |
|
268 }).filter(function (name) { |
|
269 log('Checking if the name "' + name + '" starts with "Y"...') |
|
270 return name.charAt(0) === 'Y'; |
|
271 }).then(function (names) { |
|
272 log('Done!'); |
|
273 return names; |
|
274 }).each(function (name, i) { |
|
275 log(i + '. ' + name); |
|
276 }).then(null, function (error) { |
|
277 // if there was an error in any step or request, it is automatically |
|
278 // passed around the promise chain so we can react to it at the end |
|
279 showError(error.message); |
|
280 });</pre> |
|
281 |
|
282 |
|
283 <h2 id="full-example-code">Full Example Code</h2> |
|
284 |
|
285 <pre class="code prettyprint"><script> |
|
286 YUI().use('promise', 'jsonp', 'node', 'array-extras', function (Y) { |
|
287 |
|
288 function ArrayPromise() { |
|
289 ArrayPromise.superclass.constructor.apply(this, arguments); |
|
290 } |
|
291 Y.extend(ArrayPromise, Y.Promise); |
|
292 |
|
293 // Although Y.Array.each does not return an array, for the purpose of this |
|
294 // example we make it chainable by returning the same array |
|
295 ArrayPromise.prototype.each = function (fn, thisObj) { |
|
296 return this.then(function (array) { |
|
297 Y.Array.each(array, fn, thisObj); |
|
298 return array; |
|
299 }); |
|
300 }; |
|
301 |
|
302 // Y.Array.map returns a new array, so we return the result of this.then() |
|
303 ArrayPromise.prototype.map = function (fn, thisObj) { |
|
304 return this.then(function (array) { |
|
305 // By returning the result of Y.Array.map we are returning a new promise |
|
306 // representing the new array |
|
307 return Y.Array.map(array, fn, thisObj); |
|
308 }); |
|
309 }; |
|
310 |
|
311 // Y.Array.filter follows the same pattern as Y.Array.map |
|
312 ArrayPromise.prototype.filter = function (fn, thisObj) { |
|
313 return this.then(function (array) { |
|
314 return Y.Array.filter(array, fn, thisObj); |
|
315 }); |
|
316 }; |
|
317 |
|
318 // Takes any promise and returns an ArrayPromise |
|
319 function toArrayPromise(promise) { |
|
320 return new ArrayPromise(function (fulfill, reject) { |
|
321 promise.then(fulfill, reject); |
|
322 }); |
|
323 } |
|
324 |
|
325 |
|
326 // A cache for GitHub user data |
|
327 var GitHub = (function () { |
|
328 |
|
329 var cache = {}, |
|
330 githubURL = 'https://api.github.com/users/{user}?callback={callback}'; |
|
331 |
|
332 function getUserURL(name) { |
|
333 return Y.Lang.sub(githubURL, { |
|
334 user: name |
|
335 }); |
|
336 } |
|
337 |
|
338 // Fetches a URL, stores a promise in the cache and returns it |
|
339 function fetch(url) { |
|
340 var promise = new Y.Promise(function (fulfill, reject) { |
|
341 Y.jsonp(url, function (res) { |
|
342 var meta = res.meta, |
|
343 data = res.data; |
|
344 |
|
345 // Check for a successful response, otherwise reject the |
|
346 // promise with the message returned by the GitHub API. |
|
347 if (meta.status >= 200 && meta.status < 300) { |
|
348 fulfill(data); |
|
349 } else { |
|
350 reject(new Error(data.message)); |
|
351 } |
|
352 }); |
|
353 |
|
354 // Add a timeout in case the URL is completely wrong |
|
355 // or GitHub is too busy |
|
356 setTimeout(function () { |
|
357 // Once a promise has been fulfilled or rejected it will never |
|
358 // change its state again, so we can safely call reject() after |
|
359 // some time. If it was already fulfilled or rejected, nothing will |
|
360 // happen |
|
361 reject(new Error('Timeout')); |
|
362 }, 10000); |
|
363 }); |
|
364 |
|
365 // store the promise in the cache object |
|
366 cache[url] = promise; |
|
367 |
|
368 return promise; |
|
369 } |
|
370 |
|
371 return { |
|
372 getUser: function (name) { |
|
373 var url = getUserURL(name); |
|
374 |
|
375 if (cache[url]) { |
|
376 // If we have already stored the promise in the cache we just return it |
|
377 return cache[url]; |
|
378 } else { |
|
379 // fetch() will make a JSONP request, cache the promise and return it |
|
380 return fetch(url); |
|
381 } |
|
382 } |
|
383 }; |
|
384 }()); |
|
385 |
|
386 |
|
387 var demoNode = Y.one('#demo'); |
|
388 |
|
389 function log(text) { |
|
390 demoNode.append(Y.Node.create('<div></div>').set('text', text)); |
|
391 } |
|
392 function showError(message) { |
|
393 demoNode.append( |
|
394 Y.Node.create('<div class="error"></div>').set('text', message) |
|
395 ); |
|
396 } |
|
397 |
|
398 log('Fetching GitHub data for users: "yui", "yahoo" and "davglass"...') |
|
399 |
|
400 // requests is a regular promise |
|
401 var requests = Y.batch(GitHub.getUser('yui'), GitHub.getUser('yahoo'), GitHub.getUser('davglass')); |
|
402 // users is now an ArrayPromise |
|
403 var users = toArrayPromise(requests); |
|
404 |
|
405 // Transform the data into a list of names |
|
406 users.map(function (data) { |
|
407 log('Getting name for user "' + data.login + '"...') |
|
408 return data.name; |
|
409 }).filter(function (name) { |
|
410 log('Checking if the name "' + name + '" starts with "Y"...') |
|
411 return name.charAt(0) === 'Y'; |
|
412 }).then(function (names) { |
|
413 log('Done!'); |
|
414 return names; |
|
415 }).each(function (name, i) { |
|
416 log(i + '. ' + name); |
|
417 }).then(null, function (error) { |
|
418 // if there was an error in any step or request, it is automatically |
|
419 // passed around the promise chain so we can react to it at the end |
|
420 showError(error.message); |
|
421 }); |
|
422 |
|
423 }); |
|
424 </script></pre> |
|
425 |
|
426 </div> |
|
427 </div> |
|
428 </div> |
|
429 |
|
430 <div class="yui3-u-1-4"> |
|
431 <div class="sidebar"> |
|
432 |
|
433 <div id="toc" class="sidebox"> |
|
434 <div class="hd"> |
|
435 <h2 class="no-toc">Table of Contents</h2> |
|
436 </div> |
|
437 |
|
438 <div class="bd"> |
|
439 <ul class="toc"> |
|
440 <li> |
|
441 <a href="#subclassing-ypromise">Subclassing Y.Promise</a> |
|
442 </li> |
|
443 <li> |
|
444 <a href="#method-chaining">Method Chaining</a> |
|
445 <ul class="toc"> |
|
446 <li> |
|
447 <a href="#putting-our-class-to-action">Putting our Class to Action</a> |
|
448 </li> |
|
449 </ul> |
|
450 </li> |
|
451 <li> |
|
452 <a href="#full-example-code">Full Example Code</a> |
|
453 </li> |
|
454 </ul> |
|
455 </div> |
|
456 </div> |
|
457 |
|
458 |
|
459 |
|
460 <div class="sidebox"> |
|
461 <div class="hd"> |
|
462 <h2 class="no-toc">Examples</h2> |
|
463 </div> |
|
464 |
|
465 <div class="bd"> |
|
466 <ul class="examples"> |
|
467 |
|
468 |
|
469 <li data-description="Wrapping async transactions with promises"> |
|
470 <a href="basic-example.html">Wrapping async transactions with promises</a> |
|
471 </li> |
|
472 |
|
473 |
|
474 |
|
475 <li data-description="Extend Y.Promise to create classes that encapsulate standard transaction logic in descriptive method names"> |
|
476 <a href="subclass-example.html">Subclassing Y.Promise</a> |
|
477 </li> |
|
478 |
|
479 |
|
480 |
|
481 <li data-description="Extend the Promise class to create your own Node plugin that chains transitions"> |
|
482 <a href="plugin-example.html">Creating a Node Plugin that chains transitions</a> |
|
483 </li> |
|
484 |
|
485 |
|
486 </ul> |
|
487 </div> |
|
488 </div> |
|
489 |
|
490 |
|
491 |
|
492 </div> |
|
493 </div> |
|
494 </div> |
|
495 </div> |
|
496 |
|
497 <script src="../assets/vendor/prettify/prettify-min.js"></script> |
|
498 <script>prettyPrint();</script> |
|
499 |
|
500 <script> |
|
501 YUI.Env.Tests = { |
|
502 examples: [], |
|
503 project: '../assets', |
|
504 assets: '../assets/promise', |
|
505 name: 'subclass-example', |
|
506 title: 'Subclassing Y.Promise', |
|
507 newWindow: '', |
|
508 auto: false |
|
509 }; |
|
510 YUI.Env.Tests.examples.push('basic-example'); |
|
511 YUI.Env.Tests.examples.push('subclass-example'); |
|
512 YUI.Env.Tests.examples.push('plugin-example'); |
|
513 |
|
514 </script> |
|
515 <script src="../assets/yui/test-runner.js"></script> |
|
516 |
|
517 |
|
518 |
|
519 </body> |
|
520 </html> |