OLD | NEW |
(Empty) | |
| 1 // Copyright 2012 the V8 project authors. All rights reserved. |
| 2 // Redistribution and use in source and binary forms, with or without |
| 3 // modification, are permitted provided that the following conditions are |
| 4 // met: |
| 5 // |
| 6 // * Redistributions of source code must retain the above copyright |
| 7 // notice, this list of conditions and the following disclaimer. |
| 8 // * Redistributions in binary form must reproduce the above |
| 9 // copyright notice, this list of conditions and the following |
| 10 // disclaimer in the documentation and/or other materials provided |
| 11 // with the distribution. |
| 12 // * Neither the name of Google Inc. nor the names of its |
| 13 // contributors may be used to endorse or promote products derived |
| 14 // from this software without specific prior written permission. |
| 15 // |
| 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 27 |
| 28 |
| 29 "use strict"; |
| 30 |
| 31 //This file relies on the fact that the following declaration has been made |
| 32 //in runtime.js: |
| 33 //var $Object = global.Object; |
| 34 //var $WeakMap = global.WeakMap; |
| 35 |
| 36 |
| 37 var $Promise = Promise; |
| 38 |
| 39 |
| 40 // TODO(rossberg): A temporary shim for creating microtasks. |
| 41 function Task(task) { |
| 42 var dummy = {} |
| 43 $Object.observe(dummy, task) |
| 44 dummy.dummy = dummy |
| 45 } |
| 46 |
| 47 |
| 48 //------------------------------------------------------------------- |
| 49 |
| 50 // Core functionality. |
| 51 |
| 52 // Status values: 0 = pending, +1 = resolved, -1 = rejected |
| 53 var promiseStatus = NEW_PRIVATE("Promise#status"); |
| 54 var promiseValue = NEW_PRIVATE("Promise#value"); |
| 55 var promiseOnResolve = NEW_PRIVATE("Promise#onResolve"); |
| 56 var promiseOnReject = NEW_PRIVATE("Promise#onReject"); |
| 57 |
| 58 function IsPromise(x) { |
| 59 return IS_SPEC_OBJECT(x) && %HasLocalProperty(x, promiseStatus); |
| 60 } |
| 61 |
| 62 function Promise(resolver) { |
| 63 SET_PRIVATE(this, promiseStatus, 0); |
| 64 SET_PRIVATE(this, promiseOnResolve, []); |
| 65 SET_PRIVATE(this, promiseOnReject, []); |
| 66 var that = this; |
| 67 resolver(function(x) { PromiseResolve(that, x) }, |
| 68 function(r) { PromiseReject(that, r) }); |
| 69 } |
| 70 |
| 71 function PromiseResolve(promise, x) { |
| 72 if (GET_PRIVATE(promise, promiseStatus) !== 0) |
| 73 throw MakeTypeError('promise_not_pending', [promise]); |
| 74 PromiseQueue(GET_PRIVATE(promise, promiseOnResolve), x); |
| 75 SET_PRIVATE(promise, promiseValue, x); |
| 76 SET_PRIVATE(promise, promiseOnResolve, UNDEFINED); |
| 77 SET_PRIVATE(promise, promiseOnReject, UNDEFINED); |
| 78 SET_PRIVATE(promise, promiseStatus, +1); |
| 79 } |
| 80 |
| 81 function PromiseReject(promise, r) { |
| 82 if (GET_PRIVATE(promise, promiseStatus) !== 0) |
| 83 throw MakeTypeError('promise_not_pending', [promise]); |
| 84 PromiseQueue(GET_PRIVATE(promise, promiseOnReject), r); |
| 85 SET_PRIVATE(promise, promiseValue, r); |
| 86 SET_PRIVATE(promise, promiseOnResolve, UNDEFINED); |
| 87 SET_PRIVATE(promise, promiseOnReject, UNDEFINED); |
| 88 SET_PRIVATE(promise, promiseStatus, -1); |
| 89 } |
| 90 |
| 91 function PromiseQueue(tasks, x) { |
| 92 for (var i in tasks) { |
| 93 Task(function() { tasks[i](x) }); |
| 94 } |
| 95 } |
| 96 |
| 97 |
| 98 // Convenience. |
| 99 |
| 100 function PromiseDeferred() { |
| 101 var result = {}; |
| 102 result.promise = new this(function(resolve, reject) { |
| 103 result.resolve = resolve; |
| 104 result.reject = reject; |
| 105 }) |
| 106 return result; |
| 107 } |
| 108 |
| 109 function PromiseResolved(x) { |
| 110 return new this(function(resolve, reject) { resolve(x) }); |
| 111 } |
| 112 |
| 113 function PromiseRejected(r) { |
| 114 return new this(function(resolve, reject) { reject(r) }); |
| 115 } |
| 116 |
| 117 |
| 118 // Simple chaining (a.k.a. flatMap). |
| 119 |
| 120 function PromiseNopHandler() {} |
| 121 |
| 122 function PromiseWhen(onResolve, onReject) { |
| 123 onResolve = IS_UNDEFINED(onResolve) ? PromiseNopHandler : onResolve; |
| 124 onReject = IS_UNDEFINED(onReject) ? PromiseNopHandler : onReject; |
| 125 var deferred = Promise.deferred.call(this.constructor); |
| 126 switch (GET_PRIVATE(this, promiseStatus)) { |
| 127 case UNDEFINED: |
| 128 throw MakeTypeError('not_a_promise', [this]); |
| 129 case 0: // Pending |
| 130 GET_PRIVATE(this, promiseOnResolve).push( |
| 131 PromiseChain(deferred, onResolve)); |
| 132 GET_PRIVATE(this, promiseOnReject).push( |
| 133 PromiseChain(deferred, onReject)); |
| 134 break |
| 135 case +1: // Resolved |
| 136 PromiseQueue([PromiseChain(deferred, onResolve)], |
| 137 GET_PRIVATE(this, promiseValue)); |
| 138 break |
| 139 case -1: // Rejected |
| 140 PromiseQueue([PromiseChain(deferred, onReject)], |
| 141 GET_PRIVATE(this, promiseValue)); |
| 142 break |
| 143 default: |
| 144 unreachable(); |
| 145 } |
| 146 return deferred.promise; |
| 147 } |
| 148 |
| 149 function PromiseCatch(onReject) { |
| 150 return this.when(UNDEFINED, onReject); |
| 151 } |
| 152 |
| 153 // TODO(rossberg): uncurry |
| 154 function PromiseChain(deferred, handler) { |
| 155 return function(x) { |
| 156 try { |
| 157 var y = handler(x); |
| 158 if (y === deferred.promise) |
| 159 throw MakeTypeError('promise_cyclic', [y]); |
| 160 else if (IsPromise(y)) |
| 161 y.when(deferred.resolve, deferred.reject); |
| 162 else |
| 163 deferred.resolve(y); |
| 164 } catch(e) { |
| 165 deferred.reject(e); |
| 166 } |
| 167 } |
| 168 } |
| 169 |
| 170 |
| 171 // Extended functionality for multi-unwrapping chaining and coercive 'then'. |
| 172 |
| 173 function PromiseThen(onResolve, onReject) { |
| 174 onResolve = IS_UNDEFINED(onResolve) ? PromiseNopHandler : onResolve |
| 175 var that = this |
| 176 var constructor = this.constructor |
| 177 return this.when( |
| 178 function(x) { |
| 179 x = PromiseCoerce(constructor, x) |
| 180 return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) : |
| 181 IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x) |
| 182 }, |
| 183 onReject |
| 184 ) |
| 185 } |
| 186 |
| 187 PromiseCoerce.table = new $WeakMap |
| 188 |
| 189 function PromiseCoerce(constructor, x) { |
| 190 if (IsPromise(x)) { |
| 191 return x |
| 192 } else if (!IS_NULL_OR_UNDEFINED(x) && 'then' in TO_OBJECT(x)) { |
| 193 if (PromiseCoerce.table.has(x)) { |
| 194 return PromiseCoerce.table.get(x) |
| 195 } else { |
| 196 var deferred = constructor.deferred() |
| 197 PromiseCoerce.table.set(x, deferred.promise) |
| 198 try { |
| 199 x.then(deferred.resolve, deferred.reject) |
| 200 } catch(e) { |
| 201 deferred.reject(e) |
| 202 } |
| 203 return deferred.promise |
| 204 } |
| 205 } else { |
| 206 return x |
| 207 } |
| 208 } |
| 209 |
| 210 |
| 211 // Combinators. |
| 212 |
| 213 function PromiseCast(x) { |
| 214 if (x instanceof this) return x |
| 215 if (IsPromise(x)) { |
| 216 var result = this.deferred() |
| 217 x.when(result.resolve, result.reject) |
| 218 return result.promise |
| 219 } |
| 220 return this.resolved(x) |
| 221 } |
| 222 |
| 223 function PromiseAll(values) { |
| 224 var deferred = this.deferred() |
| 225 var count = 0 |
| 226 for (var i in values) { |
| 227 ++count |
| 228 this.cast(values[i]).when( |
| 229 function(x) { if (--count === 0) deferred.resolve(undefined) }, |
| 230 function(r) { if (count > 0) { count = 0; deferred.reject(r) } } |
| 231 ) |
| 232 } |
| 233 return deferred.promise |
| 234 } |
| 235 |
| 236 function PromiseOne(values) { |
| 237 var deferred = this.deferred() |
| 238 var done = false |
| 239 for (var i in values) { |
| 240 this.cast(values[i]).when( |
| 241 function(x) { if (!done) { done = true; deferred.resolve(x) } }, |
| 242 function(r) { if (!done) { done = true; deferred.reject(r) } } |
| 243 ) |
| 244 } |
| 245 return deferred.promise |
| 246 } |
| 247 |
| 248 //------------------------------------------------------------------- |
| 249 |
| 250 function SetUpPromise() { |
| 251 %CheckIsBootstrapping() |
| 252 |
| 253 global.Promise = $Promise; |
| 254 |
| 255 InstallFunctions($Promise, DONT_ENUM, [ |
| 256 "deferred", PromiseDeferred, |
| 257 "resolved", PromiseResolved, |
| 258 "rejected", PromiseRejected, |
| 259 "all", PromiseAll, |
| 260 "one", PromiseOne, |
| 261 "cast", PromiseCast |
| 262 ]) |
| 263 |
| 264 InstallFunctions($Promise.prototype, DONT_ENUM, [ |
| 265 "when", PromiseWhen, |
| 266 "then", PromiseThen, |
| 267 "catch", PromiseCatch |
| 268 ]) |
| 269 } |
| 270 |
| 271 SetUpPromise(); |
OLD | NEW |