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