Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('promise', function (Y, NAME) {
2
 
3
/**
4
Wraps the execution of asynchronous operations, providing a promise object that
5
can be used to subscribe to the various ways the operation may terminate.
6
 
7
When the operation completes successfully, call the Resolver's `resolve()`
8
method, passing any relevant response data for subscribers.  If the operation
9
encounters an error or is unsuccessful in some way, call `reject()`, again
10
passing any relevant data for subscribers.
11
 
12
The Resolver object should be shared only with the code resposible for
13
resolving or rejecting it. Public access for the Resolver is through its
14
_promise_, which is returned from the Resolver's `promise` property. While both
15
Resolver and promise allow subscriptions to the Resolver's state changes, the
16
promise may be exposed to non-controlling code. It is the preferable interface
17
for adding subscriptions.
18
 
19
Subscribe to state changes in the Resolver with the promise's
20
`then(callback, errback)` method.  `then()` wraps the passed callbacks in a
21
new Resolver and returns the corresponding promise, allowing chaining of
22
asynchronous or synchronous operations. E.g.
23
`promise.then(someAsyncFunc).then(anotherAsyncFunc)`
24
 
25
@module promise
26
@since 3.9.0
27
**/
28
 
29
var Lang  = Y.Lang,
30
    slice = [].slice;
31
 
32
/**
33
A promise represents a value that may not yet be available. Promises allow
34
you to chain asynchronous operations, write synchronous looking code and
35
handle errors throughout the process.
36
 
37
This constructor takes a function as a parameter where you can insert the logic
38
that fulfills or rejects this promise. The fulfillment value and the rejection
39
reason can be any JavaScript value. It's encouraged that rejection reasons be
40
error objects
41
 
42
<pre><code>
43
var fulfilled = new Y.Promise(function (resolve) {
44
    resolve('I am a fulfilled promise');
45
});
46
 
47
var rejected = new Y.Promise(function (resolve, reject) {
48
    reject(new Error('I am a rejected promise'));
49
});
50
</code></pre>
51
 
52
@class Promise
53
@constructor
54
@param {Function} fn A function where to insert the logic that resolves this
55
        promise. Receives `resolve` and `reject` functions as parameters.
56
        This function is called synchronously.
57
**/
58
function Promise(fn) {
59
    if (!(this instanceof Promise)) {
60
        return new Promise(fn);
61
    }
62
 
63
    var resolver = new Promise.Resolver(this);
64
 
65
    /**
66
    A reference to the resolver object that handles this promise
67
 
68
    @property _resolver
69
    @type Object
70
    @private
71
    */
72
    this._resolver = resolver;
73
 
74
    try {
75
        fn.call(this, function (value) {
76
            resolver.resolve(value);
77
        }, function (reason) {
78
            resolver.reject(reason);
79
        });
80
    } catch (e) {
81
        resolver.reject(e);
82
    }
83
}
84
 
85
Y.mix(Promise.prototype, {
86
    /**
87
    Schedule execution of a callback to either or both of "fulfill" and
88
    "reject" resolutions for this promise. The callbacks are wrapped in a new
89
    promise and that promise is returned.  This allows operation chaining ala
90
    `functionA().then(functionB).then(functionC)` where `functionA` returns
91
    a promise, and `functionB` and `functionC` _may_ return promises.
92
 
93
    Asynchronicity of the callbacks is guaranteed.
94
 
95
    @method then
96
    @param {Function} [callback] function to execute if the promise
97
                resolves successfully
98
    @param {Function} [errback] function to execute if the promise
99
                resolves unsuccessfully
100
    @return {Promise} A promise wrapping the resolution of either "resolve" or
101
                "reject" callback
102
    **/
103
    then: function (callback, errback) {
104
        var Constructor = this.constructor,
105
            resolver = this._resolver;
106
 
107
        // using this.constructor allows for customized promises to be
108
        // returned instead of plain ones
109
        return new Constructor(function (resolve, reject) {
110
            resolver._addCallbacks(
111
                // Check if callbacks are functions. If not, default to
112
                // `resolve` and `reject` respectively.
113
                // The wrapping of the callbacks is done here and not in
114
                // `_addCallbacks` because it is a feature specific to  `then`.
115
                // If `done` is added to promises it would call `_addCallbacks`
116
                // without defaulting to anything and without wrapping
117
                typeof callback === 'function' ?
118
                    Promise._wrap(resolve, reject, callback) : resolve,
119
                typeof errback === 'function' ?
120
                    Promise._wrap(resolve, reject, errback) : reject
121
            );
122
        });
123
    },
124
 
125
    /**
126
    A shorthand for `promise.then(undefined, callback)`.
127
 
128
    Returns a new promise and the error callback gets the same treatment as in
129
    `then`: errors get caught and turned into rejections, and the return value
130
    of the callback becomes the fulfilled value of the returned promise.
131
 
132
    @method catch
133
    @param [Function] errback Callback to be called in case this promise is
134
                        rejected
135
    @return {Promise} A new promise modified by the behavior of the error
136
                        callback
137
    **/
138
    'catch': function (errback) {
139
        return this.then(undefined, errback);
140
    },
141
 
142
    /**
143
    Returns the current status of the operation. Possible results are
144
    "pending", "fulfilled", and "rejected".
145
 
146
    @method getStatus
147
    @return {String}
148
    @deprecated
149
    **/
150
    getStatus: function () {
151
        return this._resolver.getStatus();
152
    }
153
});
154
 
155
/**
156
Wraps the callback in another function to catch exceptions and turn them into
157
rejections.
158
 
159
@method _wrap
160
@param {Function} resolve Resolving function of the resolver that
161
                    handles this promise
162
@param {Function} reject Rejection function of the resolver that
163
                    handles this promise
164
@param {Function} fn Callback to wrap
165
@return {Function}
166
@private
167
**/
168
Promise._wrap = function (resolve, reject, fn) {
169
    // callbacks and errbacks only get one argument
170
    return function (valueOrReason) {
171
        var result;
172
 
173
        // Promises model exception handling through callbacks
174
        // making both synchronous and asynchronous errors behave
175
        // the same way
176
        try {
177
            // Use the argument coming in to the callback/errback from the
178
            // resolution of the parent promise.
179
            // The function must be called as a normal function, with no
180
            // special value for |this|, as per Promises A+
181
            result = fn(valueOrReason);
182
        } catch (e) {
183
            reject(e);
184
            return;
185
        }
186
 
187
        resolve(result);
188
    };
189
};
190
 
191
/**
192
Checks if an object or value is a promise. This is cross-implementation
193
compatible, so promises returned from other libraries or native components
194
that are compatible with the Promises A+ spec should be recognized by this
195
method.
196
 
197
@method isPromise
198
@param {Any} obj The object to test
199
@return {Boolean} Whether the object is a promise or not
200
@static
201
**/
202
Promise.isPromise = function (obj) {
203
    var then;
204
    // We test promises by structure to be able to identify other
205
    // implementations' promises. This is important for cross compatibility and
206
    // In particular Y.when which should recognize any kind of promise
207
    // Use try...catch when retrieving obj.then. Return false if it throws
208
    // See Promises/A+ 1.1
209
    try {
210
        then = obj.then;
211
    } catch (_) {}
212
    return typeof then === 'function';
213
};
214
 
215
/**
216
Ensures that a certain value is a promise. If it is not a promise, it wraps it
217
in one.
218
 
219
This method can be copied or inherited in subclasses. In that case it will
220
check that the value passed to it is an instance of the correct class.
221
This means that `PromiseSubclass.resolve()` will always return instances of
222
`PromiseSubclass`.
223
 
224
@method resolve
225
@param {Any} Any object that may or may not be a promise
226
@return {Promise}
227
@static
228
**/
229
Promise.resolve = function (value) {
230
    return Promise.isPromise(value) && value.constructor === this ? value :
231
        /*jshint newcap: false */
232
        new this(function (resolve) {
233
        /*jshint newcap: true */
234
            resolve(value);
235
        });
236
};
237
 
238
/**
239
A shorthand for creating a rejected promise.
240
 
241
@method reject
242
@param {Any} reason Reason for the rejection of this promise. Usually an Error
243
    Object
244
@return {Promise} A rejected promise
245
@static
246
**/
247
Promise.reject = function (reason) {
248
    /*jshint newcap: false */
249
    return new this(function (resolve, reject) {
250
    /*jshint newcap: true */
251
        reject(reason);
252
    });
253
};
254
 
255
/**
256
Returns a promise that is resolved or rejected when all values are resolved or
257
any is rejected. This is useful for waiting for the resolution of multiple
258
promises, such as reading multiple files in Node.js or making multiple XHR
259
requests in the browser.
260
 
261
@method all
262
@param {Any[]} values An array of any kind of values, promises or not. If a value is not
263
@return [Promise] A promise for an array of all the fulfillment values
264
@static
265
**/
266
Promise.all = function (values) {
267
    var Promise = this;
268
    return new Promise(function (resolve, reject) {
269
        if (!Lang.isArray(values)) {
270
            reject(new TypeError('Promise.all expects an array of values or promises'));
271
            return;
272
        }
273
 
274
        var remaining = values.length,
275
            i         = 0,
276
            length    = values.length,
277
            results   = [];
278
 
279
        function oneDone(index) {
280
            return function (value) {
281
                results[index] = value;
282
 
283
                remaining--;
284
 
285
                if (!remaining) {
286
                    resolve(results);
287
                }
288
            };
289
        }
290
 
291
        if (length < 1) {
292
            return resolve(results);
293
        }
294
 
295
        for (; i < length; i++) {
296
            Promise.resolve(values[i]).then(oneDone(i), reject);
297
        }
298
    });
299
};
300
 
301
/**
302
Returns a promise that is resolved or rejected when any of values is either
303
resolved or rejected. Can be used for providing early feedback in the UI
304
while other operations are still pending.
305
 
306
@method race
307
@param {Any[]} values An array of values or promises
308
@return {Promise}
309
@static
310
**/
311
Promise.race = function (values) {
312
    var Promise = this;
313
    return new Promise(function (resolve, reject) {
314
        if (!Lang.isArray(values)) {
315
            reject(new TypeError('Promise.race expects an array of values or promises'));
316
            return;
317
        }
318
 
319
        // just go through the list and resolve and reject at the first change
320
        // This abuses the fact that calling resolve/reject multiple times
321
        // doesn't change the state of the returned promise
322
        for (var i = 0, count = values.length; i < count; i++) {
323
            Promise.resolve(values[i]).then(resolve, reject);
324
        }
325
    });
326
};
327
 
328
Y.Promise = Promise;
329
/**
330
Represents an asynchronous operation. Provides a
331
standard API for subscribing to the moment that the operation completes either
332
successfully (`fulfill()`) or unsuccessfully (`reject()`).
333
 
334
@class Promise.Resolver
335
@constructor
336
@param {Promise} promise The promise instance this resolver will be handling
337
**/
338
function Resolver(promise) {
339
    /**
340
    List of success callbacks
341
 
342
    @property _callbacks
343
    @type Array
344
    @private
345
    **/
346
    this._callbacks = [];
347
 
348
    /**
349
    List of failure callbacks
350
 
351
    @property _errbacks
352
    @type Array
353
    @private
354
    **/
355
    this._errbacks = [];
356
 
357
    /**
358
    The promise for this Resolver.
359
 
360
    @property promise
361
    @type Promise
362
    @deprecated
363
    **/
364
    this.promise = promise;
365
 
366
    /**
367
    The status of the operation. This property may take only one of the following
368
    values: 'pending', 'fulfilled' or 'rejected'.
369
 
370
    @property _status
371
    @type String
372
    @default 'pending'
373
    @private
374
    **/
375
    this._status = 'pending';
376
 
377
    /**
378
    This value that this promise represents.
379
 
380
    @property _result
381
    @type Any
382
    @private
383
    **/
384
    this._result = null;
385
}
386
 
387
Y.mix(Resolver.prototype, {
388
    /**
389
    Resolves the promise, signaling successful completion of the
390
    represented operation. All "onFulfilled" subscriptions are executed and passed
391
    the value provided to this method. After calling `fulfill()`, `reject()` and
392
    `notify()` are disabled.
393
 
394
    @method fulfill
395
    @param {Any} value Value to pass along to the "onFulfilled" subscribers
396
    **/
397
    fulfill: function (value) {
398
        if (this._status === 'pending') {
399
            this._result = value;
400
            this._status = 'fulfilled';
401
        }
402
 
403
        if (this._status === 'fulfilled') {
404
            this._notify(this._callbacks, this._result);
405
 
406
            // Reset the callback list so that future calls to fulfill()
407
            // won't call the same callbacks again. Promises keep a list
408
            // of callbacks, they're not the same as events. In practice,
409
            // calls to fulfill() after the first one should not be made by
410
            // the user but by then()
411
            this._callbacks = [];
412
 
413
            // Once a promise gets fulfilled it can't be rejected, so
414
            // there is no point in keeping the list. Remove it to help
415
            // garbage collection
416
            this._errbacks = null;
417
        }
418
    },
419
 
420
    /**
421
    Resolves the promise, signaling *un*successful completion of the
422
    represented operation. All "onRejected" subscriptions are executed with
423
    the value provided to this method. After calling `reject()`, `resolve()`
424
    and `notify()` are disabled.
425
 
426
    @method reject
427
    @param {Any} value Value to pass along to the "reject" subscribers
428
    **/
429
    reject: function (reason) {
430
        if (this._status === 'pending') {
431
            this._result = reason;
432
            this._status = 'rejected';
433
        }
434
 
435
        if (this._status === 'rejected') {
436
            this._notify(this._errbacks, this._result);
437
 
438
            // See fulfill()
439
            this._callbacks = null;
440
            this._errbacks = [];
441
        }
442
    },
443
 
444
    /*
445
    Given a certain value A passed as a parameter, this method resolves the
446
    promise to the value A.
447
 
448
    If A is a promise, `resolve` will cause the resolver to adopt the state of A
449
    and once A is resolved, it will resolve the resolver's promise as well.
450
    This behavior "flattens" A by calling `then` recursively and essentially
451
    disallows promises-for-promises.
452
 
453
    This is the default algorithm used when using the function passed as the
454
    first argument to the promise initialization function. This means that
455
    the following code returns a promise for the value 'hello world':
456
 
457
        var promise1 = new Y.Promise(function (resolve) {
458
            resolve('hello world');
459
        });
460
        var promise2 = new Y.Promise(function (resolve) {
461
            resolve(promise1);
462
        });
463
        promise2.then(function (value) {
464
            assert(value === 'hello world'); // true
465
        });
466
 
467
    @method resolve
468
    @param [Any] value A regular JS value or a promise
469
    */
470
    resolve: function (value) {
471
        var self = this;
472
 
473
        if (Promise.isPromise(value)) {
474
            value.then(function (value) {
475
                self.resolve(value);
476
            }, function (reason) {
477
                self.reject(reason);
478
            });
479
        } else {
480
            this.fulfill(value);
481
        }
482
    },
483
 
484
    /**
485
    Schedule execution of a callback to either or both of "resolve" and
486
    "reject" resolutions for the Resolver.  The callbacks
487
    are wrapped in a new Resolver and that Resolver's corresponding promise
488
    is returned.  This allows operation chaining ala
489
    `functionA().then(functionB).then(functionC)` where `functionA` returns
490
    a promise, and `functionB` and `functionC` _may_ return promises.
491
 
492
    @method then
493
    @param {Function} [callback] function to execute if the Resolver
494
                resolves successfully
495
    @param {Function} [errback] function to execute if the Resolver
496
                resolves unsuccessfully
497
    @return {Promise} The promise of a new Resolver wrapping the resolution
498
                of either "resolve" or "reject" callback
499
    @deprecated
500
    **/
501
    then: function (callback, errback) {
502
        return this.promise.then(callback, errback);
503
    },
504
 
505
    /**
506
    Schedule execution of a callback to either or both of "resolve" and
507
    "reject" resolutions of this resolver. If the resolver is not pending,
508
    the correct callback gets called automatically.
509
 
510
    @method _addCallbacks
511
    @param {Function} [callback] function to execute if the Resolver
512
                resolves successfully
513
    @param {Function} [errback] function to execute if the Resolver
514
                resolves unsuccessfully
515
    @private
516
    **/
517
    _addCallbacks: function (callback, errback) {
518
        var callbackList = this._callbacks,
519
            errbackList  = this._errbacks,
520
            status       = this._status,
521
            result       = this._result;
522
 
523
        if (callbackList && typeof callback === 'function') {
524
            callbackList.push(callback);
525
        }
526
        if (errbackList && typeof errback === 'function') {
527
            errbackList.push(errback);
528
        }
529
 
530
        // If a promise is already fulfilled or rejected, notify the newly added
531
        // callbacks by calling fulfill() or reject()
532
        if (status === 'fulfilled') {
533
            this.fulfill(result);
534
        } else if (status === 'rejected') {
535
            this.reject(result);
536
        }
537
    },
538
 
539
    /**
540
    Returns the current status of the Resolver as a string "pending",
541
    "fulfilled", or "rejected".
542
 
543
    @method getStatus
544
    @return {String}
545
    @deprecated
546
    **/
547
    getStatus: function () {
548
        return this._status;
549
    },
550
 
551
    /**
552
    Executes an array of callbacks from a specified context, passing a set of
553
    arguments.
554
 
555
    @method _notify
556
    @param {Function[]} subs The array of subscriber callbacks
557
    @param {Any} result Value to pass the callbacks
558
    @protected
559
    **/
560
    _notify: function (subs, result) {
561
        // Since callback lists are reset synchronously, the subs list never
562
        // changes after _notify() receives it. Avoid calling Y.soon() for
563
        // an empty list
564
        if (subs.length) {
565
            // Calling all callbacks after Y.soon to guarantee
566
            // asynchronicity. Because setTimeout can cause unnecessary
567
            // delays that *can* become noticeable in some situations
568
            // (especially in Node.js)
569
            Y.soon(function () {
570
                var i, len;
571
 
572
                for (i = 0, len = subs.length; i < len; ++i) {
573
                    subs[i](result);
574
                }
575
            });
576
        }
577
    }
578
 
579
}, true);
580
 
581
Y.Promise.Resolver = Resolver;
582
/**
583
Abstraction API allowing you to interact with promises or raw values as if they
584
were promises. If a non-promise object is passed in, a new Resolver is created
585
and scheduled to resolve asynchronously with the provided value.
586
 
587
In either case, a promise is returned.  If either _callback_ or _errback_ are
588
provided, the promise returned is the one returned from calling
589
`promise.then(callback, errback)` on the provided or created promise.  If neither
590
are provided, the original promise is returned.
591
 
592
@for YUI
593
@method when
594
@param {Any} promise Promise object or value to wrap in a resolved promise
595
@param {Function} [callback] callback to execute if the promise is resolved
596
@param {Function} [errback] callback to execute if the promise is rejected
597
@return {Promise}
598
**/
599
Y.when = function (promise, callback, errback) {
600
    promise = Promise.resolve(promise);
601
 
602
    return (callback || errback) ? promise.then(callback, errback) : promise;
603
};
604
/**
605
Returns a new promise that will be resolved when all operations have completed.
606
Takes both any numer of values as arguments. If an argument is a not a promise,
607
it will be wrapped in a new promise, same as in `Y.when()`.
608
 
609
@for YUI
610
@method batch
611
@param {Any} operation* Any number of Y.Promise objects or regular JS values
612
@return {Promise} Promise to be fulfilled when all provided promises are
613
                    resolved
614
**/
615
Y.batch = function () {
616
    return Promise.all(slice.call(arguments));
617
};
618
 
619
 
620
}, '3.18.1', {"requires": ["timers"]});