1// Copyright 2012 the V8 project 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"use strict"; 6 7// This file relies on the fact that the following declaration has been made 8// in runtime.js: 9// var $Object = global.Object 10// var $WeakMap = global.WeakMap 11 12// For bootstrapper. 13 14var IsPromise; 15var PromiseCreate; 16var PromiseResolve; 17var PromiseReject; 18var PromiseChain; 19var PromiseCatch; 20var PromiseThen; 21 22// mirror-debugger.js currently uses builtins.promiseStatus. It would be nice 23// if we could move these property names into the closure below. 24// TODO(jkummerow/rossberg/yangguo): Find a better solution. 25 26// Status values: 0 = pending, +1 = resolved, -1 = rejected 27var promiseStatus = GLOBAL_PRIVATE("Promise#status"); 28var promiseValue = GLOBAL_PRIVATE("Promise#value"); 29var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve"); 30var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject"); 31var promiseRaw = GLOBAL_PRIVATE("Promise#raw"); 32 33(function() { 34 35 var $Promise = function Promise(resolver) { 36 if (resolver === promiseRaw) return; 37 if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]); 38 if (!IS_SPEC_FUNCTION(resolver)) 39 throw MakeTypeError('resolver_not_a_function', [resolver]); 40 var promise = PromiseInit(this); 41 try { 42 %DebugPromiseHandlePrologue(function() { return promise }); 43 resolver(function(x) { PromiseResolve(promise, x) }, 44 function(r) { PromiseReject(promise, r) }); 45 } catch (e) { 46 PromiseReject(promise, e); 47 } finally { 48 %DebugPromiseHandleEpilogue(); 49 } 50 } 51 52 // Core functionality. 53 54 function PromiseSet(promise, status, value, onResolve, onReject) { 55 SET_PRIVATE(promise, promiseStatus, status); 56 SET_PRIVATE(promise, promiseValue, value); 57 SET_PRIVATE(promise, promiseOnResolve, onResolve); 58 SET_PRIVATE(promise, promiseOnReject, onReject); 59 return promise; 60 } 61 62 function PromiseInit(promise) { 63 return PromiseSet( 64 promise, 0, UNDEFINED, new InternalArray, new InternalArray) 65 } 66 67 function PromiseDone(promise, status, value, promiseQueue) { 68 if (GET_PRIVATE(promise, promiseStatus) === 0) { 69 PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue)); 70 PromiseSet(promise, status, value); 71 } 72 } 73 74 function PromiseCoerce(constructor, x) { 75 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) { 76 var then; 77 try { 78 then = x.then; 79 } catch(r) { 80 return %_CallFunction(constructor, r, PromiseRejected); 81 } 82 if (IS_SPEC_FUNCTION(then)) { 83 var deferred = %_CallFunction(constructor, PromiseDeferred); 84 try { 85 %_CallFunction(x, deferred.resolve, deferred.reject, then); 86 } catch(r) { 87 deferred.reject(r); 88 } 89 return deferred.promise; 90 } 91 } 92 return x; 93 } 94 95 function PromiseHandle(value, handler, deferred) { 96 try { 97 %DebugPromiseHandlePrologue( 98 function() { 99 var queue = GET_PRIVATE(deferred.promise, promiseOnReject); 100 return (queue && queue.length == 0) ? deferred.promise : UNDEFINED; 101 }); 102 var result = handler(value); 103 if (result === deferred.promise) 104 throw MakeTypeError('promise_cyclic', [result]); 105 else if (IsPromise(result)) 106 %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain); 107 else 108 deferred.resolve(result); 109 } catch (exception) { 110 try { 111 %DebugPromiseHandlePrologue(function() { return deferred.promise }); 112 deferred.reject(exception); 113 } catch (e) { } finally { 114 %DebugPromiseHandleEpilogue(); 115 } 116 } finally { 117 %DebugPromiseHandleEpilogue(); 118 } 119 } 120 121 function PromiseEnqueue(value, tasks) { 122 %EnqueueMicrotask(function() { 123 for (var i = 0; i < tasks.length; i += 2) { 124 PromiseHandle(value, tasks[i], tasks[i + 1]) 125 } 126 }); 127 } 128 129 function PromiseIdResolveHandler(x) { return x } 130 function PromiseIdRejectHandler(r) { throw r } 131 132 function PromiseNopResolver() {} 133 134 // ------------------------------------------------------------------- 135 // Define exported functions. 136 137 // For bootstrapper. 138 139 IsPromise = function IsPromise(x) { 140 return IS_SPEC_OBJECT(x) && HAS_PRIVATE(x, promiseStatus); 141 } 142 143 PromiseCreate = function PromiseCreate() { 144 return new $Promise(PromiseNopResolver) 145 } 146 147 PromiseResolve = function PromiseResolve(promise, x) { 148 PromiseDone(promise, +1, x, promiseOnResolve) 149 } 150 151 PromiseReject = function PromiseReject(promise, r) { 152 PromiseDone(promise, -1, r, promiseOnReject) 153 } 154 155 // Convenience. 156 157 function PromiseDeferred() { 158 if (this === $Promise) { 159 // Optimized case, avoid extra closure. 160 var promise = PromiseInit(new $Promise(promiseRaw)); 161 return { 162 promise: promise, 163 resolve: function(x) { PromiseResolve(promise, x) }, 164 reject: function(r) { PromiseReject(promise, r) } 165 }; 166 } else { 167 var result = {}; 168 result.promise = new this(function(resolve, reject) { 169 result.resolve = resolve; 170 result.reject = reject; 171 }) 172 return result; 173 } 174 } 175 176 function PromiseResolved(x) { 177 if (this === $Promise) { 178 // Optimized case, avoid extra closure. 179 return PromiseSet(new $Promise(promiseRaw), +1, x); 180 } else { 181 return new this(function(resolve, reject) { resolve(x) }); 182 } 183 } 184 185 function PromiseRejected(r) { 186 if (this === $Promise) { 187 // Optimized case, avoid extra closure. 188 return PromiseSet(new $Promise(promiseRaw), -1, r); 189 } else { 190 return new this(function(resolve, reject) { reject(r) }); 191 } 192 } 193 194 // Simple chaining. 195 196 PromiseChain = function PromiseChain(onResolve, onReject) { // a.k.a. 197 // flatMap 198 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; 199 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; 200 var deferred = %_CallFunction(this.constructor, PromiseDeferred); 201 switch (GET_PRIVATE(this, promiseStatus)) { 202 case UNDEFINED: 203 throw MakeTypeError('not_a_promise', [this]); 204 case 0: // Pending 205 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred); 206 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred); 207 break; 208 case +1: // Resolved 209 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred]); 210 break; 211 case -1: // Rejected 212 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred]); 213 break; 214 } 215 return deferred.promise; 216 } 217 218 PromiseCatch = function PromiseCatch(onReject) { 219 return this.then(UNDEFINED, onReject); 220 } 221 222 // Multi-unwrapped chaining with thenable coercion. 223 224 PromiseThen = function PromiseThen(onResolve, onReject) { 225 onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve 226 : PromiseIdResolveHandler; 227 onReject = IS_SPEC_FUNCTION(onReject) ? onReject 228 : PromiseIdRejectHandler; 229 var that = this; 230 var constructor = this.constructor; 231 return %_CallFunction( 232 this, 233 function(x) { 234 x = PromiseCoerce(constructor, x); 235 return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) : 236 IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x); 237 }, 238 onReject, 239 PromiseChain 240 ); 241 } 242 243 // Combinators. 244 245 function PromiseCast(x) { 246 // TODO(rossberg): cannot do better until we support @@create. 247 return IsPromise(x) ? x : new this(function(resolve) { resolve(x) }); 248 } 249 250 function PromiseAll(values) { 251 var deferred = %_CallFunction(this, PromiseDeferred); 252 var resolutions = []; 253 if (!%_IsArray(values)) { 254 deferred.reject(MakeTypeError('invalid_argument')); 255 return deferred.promise; 256 } 257 try { 258 var count = values.length; 259 if (count === 0) { 260 deferred.resolve(resolutions); 261 } else { 262 for (var i = 0; i < values.length; ++i) { 263 this.resolve(values[i]).then( 264 function(i, x) { 265 resolutions[i] = x; 266 if (--count === 0) deferred.resolve(resolutions); 267 }.bind(UNDEFINED, i), // TODO(rossberg): use let loop once 268 // available 269 function(r) { deferred.reject(r) } 270 ); 271 } 272 } 273 } catch (e) { 274 deferred.reject(e) 275 } 276 return deferred.promise; 277 } 278 279 function PromiseOne(values) { 280 var deferred = %_CallFunction(this, PromiseDeferred); 281 if (!%_IsArray(values)) { 282 deferred.reject(MakeTypeError('invalid_argument')); 283 return deferred.promise; 284 } 285 try { 286 for (var i = 0; i < values.length; ++i) { 287 this.resolve(values[i]).then( 288 function(x) { deferred.resolve(x) }, 289 function(r) { deferred.reject(r) } 290 ); 291 } 292 } catch (e) { 293 deferred.reject(e) 294 } 295 return deferred.promise; 296 } 297 298 // ------------------------------------------------------------------- 299 // Install exported functions. 300 301 %CheckIsBootstrapping(); 302 %SetProperty(global, 'Promise', $Promise, DONT_ENUM); 303 InstallFunctions($Promise, DONT_ENUM, [ 304 "defer", PromiseDeferred, 305 "accept", PromiseResolved, 306 "reject", PromiseRejected, 307 "all", PromiseAll, 308 "race", PromiseOne, 309 "resolve", PromiseCast 310 ]); 311 InstallFunctions($Promise.prototype, DONT_ENUM, [ 312 "chain", PromiseChain, 313 "then", PromiseThen, 314 "catch", PromiseCatch 315 ]); 316 317})(); 318