blob: f0681c123c99662db983e093bb8bc1382850c84f [file] [log] [blame]
Jamie Gennis01058352012-05-06 12:48:05 -07001// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview This implementes a future promise class.
7 */
8
9cr.define('cr', function() {
10
11 /**
12 * Sentinel used to mark a value as pending.
13 * @const
14 */
15 var PENDING_VALUE = {};
16
17 /**
18 * Creates a future promise.
19 * @param {*=} opt_value The value to set the promise to. If set completes
20 * the promise immediately.
21 * @constructor
22 */
23 function Promise(opt_value) {
24 /**
25 * An array of the callbacks.
26 * @type {!Array.<!Function>}
27 * @private
28 */
29 this.callbacks_ = [];
30
31 if (arguments.length > 0)
32 this.value = opt_value;
33 }
34
35 Promise.prototype = {
36 /**
37 * The current value.
38 * @type {*}
39 * @private
40 */
41 value_: PENDING_VALUE,
42
43 /**
44 * The value of the future promise. Accessing this before the promise has
45 * been fulfilled will throw an error. If this is set to an exception
46 * accessing this will throw as well.
47 * @type {*}
48 */
49 get value() {
50 return this.done ? this.value_ : undefined;
51 },
52 set value(value) {
53 if (!this.done) {
54 this.value_ = value;
55 for (var i = 0; i < this.callbacks_.length; i++) {
56 this.callbacks_[i].call(null, value);
57 }
58 this.callbacks_.length = 0;
59 }
60 },
61
62 /**
63 * Whether the future promise has been fulfilled.
64 * @type {boolean}
65 */
66 get done() {
67 return this.value_ !== PENDING_VALUE;
68 },
69
70 /**
71 * Adds a listener to the future promise. The function will be called when
72 * the promise is fulfilled. If the promise is already fullfilled this will
73 * never call the function.
74 * @param {!Function} fun The function to call.
75 */
76 addListener: function(fun) {
77 if (this.done)
78 fun(this.value);
79 else
80 this.callbacks_.push(fun);
81 },
82
83 /**
84 * Removes a previously added listener from the future promise.
85 * @param {!Function} fun The function to remove.
86 */
87 removeListener: function(fun) {
88 var i = this.callbacks_.indexOf(fun);
89 if (i >= 0)
90 this.callbacks_.splice(i, 1);
91 },
92
93 /**
94 * If the promise is done then this returns the string representation of
95 * the value.
96 * @return {string} The string representation of the promise.
97 * @override
98 */
99 toString: function() {
100 if (this.done)
101 return String(this.value);
102 else
103 return '[object Promise]';
104 },
105
106 /**
107 * Override to allow arithmetic.
108 * @override
109 */
110 valueOf: function() {
111 return this.value;
112 }
113 };
114
115 /**
116 * When a future promise is done call {@code fun}. This also calls the
117 * function if the promise has already been fulfilled.
118 * @param {!Promise} p The promise.
119 * @param {!Function} fun The function to call when the promise is fulfilled.
120 */
121 Promise.when = function(p, fun) {
122 p.addListener(fun);
123 };
124
125 /**
126 * Creates a new promise the will be fulfilled after {@code t} ms.
127 * @param {number} t The time to wait before the promise is fulfilled.
128 * @param {*=} opt_value The value to return after the wait.
129 * @return {!Promise} The new future promise.
130 */
131 Promise.wait = function(t, opt_value) {
132 var p = new Promise;
133 window.setTimeout(function() {
134 p.value = opt_value;
135 }, t);
136 return p;
137 };
138
139 /**
140 * Creates a new future promise that is fulfilled when any of the promises are
141 * fulfilled. The value of the returned promise will be the value of the first
142 * fulfilled promise.
143 * @param {...!Promise} var_args The promises used to build up the new
144 * promise.
145 * @return {!Promise} The new promise that will be fulfilled when any of the
146 * passed in promises are fulfilled.
147 */
148 Promise.any = function(var_args) {
149 var p = new Promise;
150 function f(v) {
151 p.value = v;
152 }
153 for (var i = 0; i < arguments.length; i++) {
154 arguments[i].addListener(f);
155 }
156 return p;
157 };
158
159 /**
160 * Creates a new future promise that is fulfilled when all of the promises are
161 * fulfilled. The value of the returned promise is an array of the values of
162 * the promises passed in.
163 * @param {...!Promise} var_args The promises used to build up the new
164 * promise.
165 * @return {!Promise} The promise that wraps all the promises in the array.
166 */
167 Promise.all = function(var_args) {
168 var p = new Promise;
169 var args = Array.prototype.slice.call(arguments);
170 var count = args.length;
171 if (!count) {
172 p.value = [];
173 return p;
174 }
175
176 function f(v) {
177 count--;
178 if (!count) {
179 p.value = args.map(function(argP) {
180 return argP.value;
181 });
182 }
183 }
184
185 // Do not use count here since count may be decremented in the call to
186 // addListener if the promise is already done.
187 for (var i = 0; i < args.length; i++) {
188 args[i].addListener(f);
189 }
190
191 return p;
192 };
193
194 /**
195 * Wraps an event in a future promise.
196 * @param {!EventTarget} target The object that dispatches the event.
197 * @param {string} type The type of the event.
198 * @param {boolean=} opt_useCapture Whether to listen to the capture phase or
199 * the bubble phase.
200 * @return {!Promise} The promise that will be fulfilled when the event is
201 * dispatched.
202 */
203 Promise.event = function(target, type, opt_useCapture) {
204 var p = new Promise;
205 target.addEventListener(type, function(e) {
206 p.value = e;
207 }, opt_useCapture);
208 return p;
209 };
210
211 return {
212 Promise: Promise
213 };
214});