github.com/nuvolaris/goja@v0.0.0-20230825100449-967811910c6d/builtin_promise.go (about) 1 package goja 2 3 import ( 4 "reflect" 5 6 "github.com/nuvolaris/goja/unistring" 7 ) 8 9 type PromiseState int 10 type PromiseRejectionOperation int 11 12 type promiseReactionType int 13 14 const ( 15 PromiseStatePending PromiseState = iota 16 PromiseStateFulfilled 17 PromiseStateRejected 18 ) 19 20 const ( 21 PromiseRejectionReject PromiseRejectionOperation = iota 22 PromiseRejectionHandle 23 ) 24 25 const ( 26 promiseReactionFulfill promiseReactionType = iota 27 promiseReactionReject 28 ) 29 30 type PromiseRejectionTracker func(p *Promise, operation PromiseRejectionOperation) 31 32 type jobCallback struct { 33 callback func(FunctionCall) Value 34 } 35 36 type promiseCapability struct { 37 promise *Object 38 resolveObj, rejectObj *Object 39 } 40 41 type promiseReaction struct { 42 capability *promiseCapability 43 typ promiseReactionType 44 handler *jobCallback 45 asyncRunner *asyncRunner 46 asyncCtx interface{} 47 } 48 49 var typePromise = reflect.TypeOf((*Promise)(nil)) 50 51 // Promise is a Go wrapper around ECMAScript Promise. Calling Runtime.ToValue() on it 52 // returns the underlying Object. Calling Export() on a Promise Object returns a Promise. 53 // 54 // Use Runtime.NewPromise() to create one. Calling Runtime.ToValue() on a zero object or nil returns null Value. 55 // 56 // WARNING: Instances of Promise are not goroutine-safe. See Runtime.NewPromise() for more details. 57 type Promise struct { 58 baseObject 59 state PromiseState 60 result Value 61 fulfillReactions []*promiseReaction 62 rejectReactions []*promiseReaction 63 handled bool 64 } 65 66 func (p *Promise) State() PromiseState { 67 return p.state 68 } 69 70 func (p *Promise) Result() Value { 71 return p.result 72 } 73 74 func (p *Promise) toValue(r *Runtime) Value { 75 if p == nil || p.val == nil { 76 return _null 77 } 78 promise := p.val 79 if promise.runtime != r { 80 panic(r.NewTypeError("Illegal runtime transition of a Promise")) 81 } 82 return promise 83 } 84 85 func (p *Promise) createResolvingFunctions() (resolve, reject *Object) { 86 r := p.val.runtime 87 alreadyResolved := false 88 return p.val.runtime.newNativeFunc(func(call FunctionCall) Value { 89 if alreadyResolved { 90 return _undefined 91 } 92 alreadyResolved = true 93 resolution := call.Argument(0) 94 if resolution.SameAs(p.val) { 95 return p.reject(r.NewTypeError("Promise self-resolution")) 96 } 97 if obj, ok := resolution.(*Object); ok { 98 var thenAction Value 99 ex := r.vm.try(func() { 100 thenAction = obj.self.getStr("then", nil) 101 }) 102 if ex != nil { 103 return p.reject(ex.val) 104 } 105 if call, ok := assertCallable(thenAction); ok { 106 job := r.newPromiseResolveThenableJob(p, resolution, &jobCallback{callback: call}) 107 r.enqueuePromiseJob(job) 108 return _undefined 109 } 110 } 111 return p.fulfill(resolution) 112 }, nil, "", nil, 1), 113 p.val.runtime.newNativeFunc(func(call FunctionCall) Value { 114 if alreadyResolved { 115 return _undefined 116 } 117 alreadyResolved = true 118 reason := call.Argument(0) 119 return p.reject(reason) 120 }, nil, "", nil, 1) 121 } 122 123 func (p *Promise) reject(reason Value) Value { 124 reactions := p.rejectReactions 125 p.result = reason 126 p.fulfillReactions, p.rejectReactions = nil, nil 127 p.state = PromiseStateRejected 128 r := p.val.runtime 129 if !p.handled { 130 r.trackPromiseRejection(p, PromiseRejectionReject) 131 } 132 r.triggerPromiseReactions(reactions, reason) 133 return _undefined 134 } 135 136 func (p *Promise) fulfill(value Value) Value { 137 reactions := p.fulfillReactions 138 p.result = value 139 p.fulfillReactions, p.rejectReactions = nil, nil 140 p.state = PromiseStateFulfilled 141 p.val.runtime.triggerPromiseReactions(reactions, value) 142 return _undefined 143 } 144 145 func (p *Promise) exportType() reflect.Type { 146 return typePromise 147 } 148 149 func (p *Promise) export(*objectExportCtx) interface{} { 150 return p 151 } 152 153 func (p *Promise) addReactions(fulfillReaction *promiseReaction, rejectReaction *promiseReaction) { 154 r := p.val.runtime 155 if tracker := r.asyncContextTracker; tracker != nil { 156 ctx := tracker.Grab() 157 fulfillReaction.asyncCtx = ctx 158 rejectReaction.asyncCtx = ctx 159 } 160 switch p.state { 161 case PromiseStatePending: 162 p.fulfillReactions = append(p.fulfillReactions, fulfillReaction) 163 p.rejectReactions = append(p.rejectReactions, rejectReaction) 164 case PromiseStateFulfilled: 165 r.enqueuePromiseJob(r.newPromiseReactionJob(fulfillReaction, p.result)) 166 default: 167 reason := p.result 168 if !p.handled { 169 r.trackPromiseRejection(p, PromiseRejectionHandle) 170 } 171 r.enqueuePromiseJob(r.newPromiseReactionJob(rejectReaction, reason)) 172 } 173 p.handled = true 174 } 175 176 func (r *Runtime) newPromiseResolveThenableJob(p *Promise, thenable Value, then *jobCallback) func() { 177 return func() { 178 resolve, reject := p.createResolvingFunctions() 179 ex := r.vm.try(func() { 180 r.callJobCallback(then, thenable, resolve, reject) 181 }) 182 if ex != nil { 183 if fn, ok := reject.self.assertCallable(); ok { 184 fn(FunctionCall{Arguments: []Value{ex.val}}) 185 } 186 } 187 } 188 } 189 190 func (r *Runtime) enqueuePromiseJob(job func()) { 191 r.jobQueue = append(r.jobQueue, job) 192 } 193 194 func (r *Runtime) triggerPromiseReactions(reactions []*promiseReaction, argument Value) { 195 for _, reaction := range reactions { 196 r.enqueuePromiseJob(r.newPromiseReactionJob(reaction, argument)) 197 } 198 } 199 200 func (r *Runtime) newPromiseReactionJob(reaction *promiseReaction, argument Value) func() { 201 return func() { 202 var handlerResult Value 203 fulfill := false 204 if reaction.handler == nil { 205 handlerResult = argument 206 if reaction.typ == promiseReactionFulfill { 207 fulfill = true 208 } 209 } else { 210 if tracker := r.asyncContextTracker; tracker != nil { 211 tracker.Resumed(reaction.asyncCtx) 212 } 213 ex := r.vm.try(func() { 214 handlerResult = r.callJobCallback(reaction.handler, _undefined, argument) 215 fulfill = true 216 }) 217 if ex != nil { 218 handlerResult = ex.val 219 } 220 if tracker := r.asyncContextTracker; tracker != nil { 221 tracker.Exited() 222 } 223 } 224 if reaction.capability != nil { 225 if fulfill { 226 reaction.capability.resolve(handlerResult) 227 } else { 228 reaction.capability.reject(handlerResult) 229 } 230 } 231 } 232 } 233 234 func (r *Runtime) newPromise(proto *Object) *Promise { 235 o := &Object{runtime: r} 236 237 po := &Promise{} 238 po.class = classObject 239 po.val = o 240 po.extensible = true 241 o.self = po 242 po.prototype = proto 243 po.init() 244 return po 245 } 246 247 func (r *Runtime) builtin_newPromise(args []Value, newTarget *Object) *Object { 248 if newTarget == nil { 249 panic(r.needNew("Promise")) 250 } 251 var arg0 Value 252 if len(args) > 0 { 253 arg0 = args[0] 254 } 255 executor := r.toCallable(arg0) 256 257 proto := r.getPrototypeFromCtor(newTarget, r.global.Promise, r.global.PromisePrototype) 258 po := r.newPromise(proto) 259 260 resolve, reject := po.createResolvingFunctions() 261 ex := r.vm.try(func() { 262 executor(FunctionCall{Arguments: []Value{resolve, reject}}) 263 }) 264 if ex != nil { 265 if fn, ok := reject.self.assertCallable(); ok { 266 fn(FunctionCall{Arguments: []Value{ex.val}}) 267 } 268 } 269 return po.val 270 } 271 272 func (r *Runtime) promiseProto_then(call FunctionCall) Value { 273 thisObj := r.toObject(call.This) 274 if p, ok := thisObj.self.(*Promise); ok { 275 c := r.speciesConstructorObj(thisObj, r.global.Promise) 276 resultCapability := r.newPromiseCapability(c) 277 return r.performPromiseThen(p, call.Argument(0), call.Argument(1), resultCapability) 278 } 279 panic(r.NewTypeError("Method Promise.prototype.then called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) 280 } 281 282 func (r *Runtime) newPromiseCapability(c *Object) *promiseCapability { 283 pcap := new(promiseCapability) 284 if c == r.global.Promise { 285 p := r.newPromise(r.global.PromisePrototype) 286 pcap.resolveObj, pcap.rejectObj = p.createResolvingFunctions() 287 pcap.promise = p.val 288 } else { 289 var resolve, reject Value 290 executor := r.newNativeFunc(func(call FunctionCall) Value { 291 if resolve != nil { 292 panic(r.NewTypeError("resolve is already set")) 293 } 294 if reject != nil { 295 panic(r.NewTypeError("reject is already set")) 296 } 297 if arg := call.Argument(0); arg != _undefined { 298 resolve = arg 299 } 300 if arg := call.Argument(1); arg != _undefined { 301 reject = arg 302 } 303 return nil 304 }, nil, "", nil, 2) 305 pcap.promise = r.toConstructor(c)([]Value{executor}, c) 306 pcap.resolveObj = r.toObject(resolve) 307 r.toCallable(pcap.resolveObj) // make sure it's callable 308 pcap.rejectObj = r.toObject(reject) 309 r.toCallable(pcap.rejectObj) 310 } 311 return pcap 312 } 313 314 func (r *Runtime) performPromiseThen(p *Promise, onFulfilled, onRejected Value, resultCapability *promiseCapability) Value { 315 var onFulfilledJobCallback, onRejectedJobCallback *jobCallback 316 if f, ok := assertCallable(onFulfilled); ok { 317 onFulfilledJobCallback = &jobCallback{callback: f} 318 } 319 if f, ok := assertCallable(onRejected); ok { 320 onRejectedJobCallback = &jobCallback{callback: f} 321 } 322 fulfillReaction := &promiseReaction{ 323 capability: resultCapability, 324 typ: promiseReactionFulfill, 325 handler: onFulfilledJobCallback, 326 } 327 rejectReaction := &promiseReaction{ 328 capability: resultCapability, 329 typ: promiseReactionReject, 330 handler: onRejectedJobCallback, 331 } 332 p.addReactions(fulfillReaction, rejectReaction) 333 if resultCapability == nil { 334 return _undefined 335 } 336 return resultCapability.promise 337 } 338 339 func (r *Runtime) promiseProto_catch(call FunctionCall) Value { 340 return r.invoke(call.This, "then", _undefined, call.Argument(0)) 341 } 342 343 func (r *Runtime) promiseResolve(c *Object, x Value) *Object { 344 if obj, ok := x.(*Object); ok { 345 xConstructor := nilSafe(obj.self.getStr("constructor", nil)) 346 if xConstructor.SameAs(c) { 347 return obj 348 } 349 } 350 pcap := r.newPromiseCapability(c) 351 pcap.resolve(x) 352 return pcap.promise 353 } 354 355 func (r *Runtime) promiseProto_finally(call FunctionCall) Value { 356 promise := r.toObject(call.This) 357 c := r.speciesConstructorObj(promise, r.global.Promise) 358 onFinally := call.Argument(0) 359 var thenFinally, catchFinally Value 360 if onFinallyFn, ok := assertCallable(onFinally); !ok { 361 thenFinally, catchFinally = onFinally, onFinally 362 } else { 363 thenFinally = r.newNativeFunc(func(call FunctionCall) Value { 364 value := call.Argument(0) 365 result := onFinallyFn(FunctionCall{}) 366 promise := r.promiseResolve(c, result) 367 valueThunk := r.newNativeFunc(func(call FunctionCall) Value { 368 return value 369 }, nil, "", nil, 0) 370 return r.invoke(promise, "then", valueThunk) 371 }, nil, "", nil, 1) 372 373 catchFinally = r.newNativeFunc(func(call FunctionCall) Value { 374 reason := call.Argument(0) 375 result := onFinallyFn(FunctionCall{}) 376 promise := r.promiseResolve(c, result) 377 thrower := r.newNativeFunc(func(call FunctionCall) Value { 378 panic(reason) 379 }, nil, "", nil, 0) 380 return r.invoke(promise, "then", thrower) 381 }, nil, "", nil, 1) 382 } 383 return r.invoke(promise, "then", thenFinally, catchFinally) 384 } 385 386 func (pcap *promiseCapability) resolve(result Value) { 387 pcap.promise.runtime.toCallable(pcap.resolveObj)(FunctionCall{Arguments: []Value{result}}) 388 } 389 390 func (pcap *promiseCapability) reject(reason Value) { 391 pcap.promise.runtime.toCallable(pcap.rejectObj)(FunctionCall{Arguments: []Value{reason}}) 392 } 393 394 func (pcap *promiseCapability) try(f func()) bool { 395 ex := pcap.promise.runtime.vm.try(f) 396 if ex != nil { 397 pcap.reject(ex.val) 398 return false 399 } 400 return true 401 } 402 403 func (r *Runtime) promise_all(call FunctionCall) Value { 404 c := r.toObject(call.This) 405 pcap := r.newPromiseCapability(c) 406 407 pcap.try(func() { 408 promiseResolve := r.toCallable(c.self.getStr("resolve", nil)) 409 iter := r.getIterator(call.Argument(0), nil) 410 var values []Value 411 remainingElementsCount := 1 412 iter.iterate(func(nextValue Value) { 413 index := len(values) 414 values = append(values, _undefined) 415 nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}}) 416 alreadyCalled := false 417 onFulfilled := r.newNativeFunc(func(call FunctionCall) Value { 418 if alreadyCalled { 419 return _undefined 420 } 421 alreadyCalled = true 422 values[index] = call.Argument(0) 423 remainingElementsCount-- 424 if remainingElementsCount == 0 { 425 pcap.resolve(r.newArrayValues(values)) 426 } 427 return _undefined 428 }, nil, "", nil, 1) 429 remainingElementsCount++ 430 r.invoke(nextPromise, "then", onFulfilled, pcap.rejectObj) 431 }) 432 remainingElementsCount-- 433 if remainingElementsCount == 0 { 434 pcap.resolve(r.newArrayValues(values)) 435 } 436 }) 437 return pcap.promise 438 } 439 440 func (r *Runtime) promise_allSettled(call FunctionCall) Value { 441 c := r.toObject(call.This) 442 pcap := r.newPromiseCapability(c) 443 444 pcap.try(func() { 445 promiseResolve := r.toCallable(c.self.getStr("resolve", nil)) 446 iter := r.getIterator(call.Argument(0), nil) 447 var values []Value 448 remainingElementsCount := 1 449 iter.iterate(func(nextValue Value) { 450 index := len(values) 451 values = append(values, _undefined) 452 nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}}) 453 alreadyCalled := false 454 reaction := func(status Value, valueKey unistring.String) *Object { 455 return r.newNativeFunc(func(call FunctionCall) Value { 456 if alreadyCalled { 457 return _undefined 458 } 459 alreadyCalled = true 460 obj := r.NewObject() 461 obj.self._putProp("status", status, true, true, true) 462 obj.self._putProp(valueKey, call.Argument(0), true, true, true) 463 values[index] = obj 464 remainingElementsCount-- 465 if remainingElementsCount == 0 { 466 pcap.resolve(r.newArrayValues(values)) 467 } 468 return _undefined 469 }, nil, "", nil, 1) 470 } 471 onFulfilled := reaction(asciiString("fulfilled"), "value") 472 onRejected := reaction(asciiString("rejected"), "reason") 473 remainingElementsCount++ 474 r.invoke(nextPromise, "then", onFulfilled, onRejected) 475 }) 476 remainingElementsCount-- 477 if remainingElementsCount == 0 { 478 pcap.resolve(r.newArrayValues(values)) 479 } 480 }) 481 return pcap.promise 482 } 483 484 func (r *Runtime) promise_any(call FunctionCall) Value { 485 c := r.toObject(call.This) 486 pcap := r.newPromiseCapability(c) 487 488 pcap.try(func() { 489 promiseResolve := r.toCallable(c.self.getStr("resolve", nil)) 490 iter := r.getIterator(call.Argument(0), nil) 491 var errors []Value 492 remainingElementsCount := 1 493 iter.iterate(func(nextValue Value) { 494 index := len(errors) 495 errors = append(errors, _undefined) 496 nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}}) 497 alreadyCalled := false 498 onRejected := r.newNativeFunc(func(call FunctionCall) Value { 499 if alreadyCalled { 500 return _undefined 501 } 502 alreadyCalled = true 503 errors[index] = call.Argument(0) 504 remainingElementsCount-- 505 if remainingElementsCount == 0 { 506 _error := r.builtin_new(r.global.AggregateError, nil) 507 _error.self._putProp("errors", r.newArrayValues(errors), true, false, true) 508 pcap.reject(_error) 509 } 510 return _undefined 511 }, nil, "", nil, 1) 512 513 remainingElementsCount++ 514 r.invoke(nextPromise, "then", pcap.resolveObj, onRejected) 515 }) 516 remainingElementsCount-- 517 if remainingElementsCount == 0 { 518 _error := r.builtin_new(r.global.AggregateError, nil) 519 _error.self._putProp("errors", r.newArrayValues(errors), true, false, true) 520 pcap.reject(_error) 521 } 522 }) 523 return pcap.promise 524 } 525 526 func (r *Runtime) promise_race(call FunctionCall) Value { 527 c := r.toObject(call.This) 528 pcap := r.newPromiseCapability(c) 529 530 pcap.try(func() { 531 promiseResolve := r.toCallable(c.self.getStr("resolve", nil)) 532 iter := r.getIterator(call.Argument(0), nil) 533 iter.iterate(func(nextValue Value) { 534 nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}}) 535 r.invoke(nextPromise, "then", pcap.resolveObj, pcap.rejectObj) 536 }) 537 }) 538 return pcap.promise 539 } 540 541 func (r *Runtime) promise_reject(call FunctionCall) Value { 542 pcap := r.newPromiseCapability(r.toObject(call.This)) 543 pcap.reject(call.Argument(0)) 544 return pcap.promise 545 } 546 547 func (r *Runtime) promise_resolve(call FunctionCall) Value { 548 return r.promiseResolve(r.toObject(call.This), call.Argument(0)) 549 } 550 551 func (r *Runtime) createPromiseProto(val *Object) objectImpl { 552 o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) 553 o._putProp("constructor", r.global.Promise, true, false, true) 554 555 o._putProp("catch", r.newNativeFunc(r.promiseProto_catch, nil, "catch", nil, 1), true, false, true) 556 o._putProp("finally", r.newNativeFunc(r.promiseProto_finally, nil, "finally", nil, 1), true, false, true) 557 o._putProp("then", r.newNativeFunc(r.promiseProto_then, nil, "then", nil, 2), true, false, true) 558 559 o._putSym(SymToStringTag, valueProp(asciiString(classPromise), false, false, true)) 560 561 return o 562 } 563 564 func (r *Runtime) createPromise(val *Object) objectImpl { 565 o := r.newNativeConstructOnly(val, r.builtin_newPromise, r.global.PromisePrototype, "Promise", 1) 566 567 o._putProp("all", r.newNativeFunc(r.promise_all, nil, "all", nil, 1), true, false, true) 568 o._putProp("allSettled", r.newNativeFunc(r.promise_allSettled, nil, "allSettled", nil, 1), true, false, true) 569 o._putProp("any", r.newNativeFunc(r.promise_any, nil, "any", nil, 1), true, false, true) 570 o._putProp("race", r.newNativeFunc(r.promise_race, nil, "race", nil, 1), true, false, true) 571 o._putProp("reject", r.newNativeFunc(r.promise_reject, nil, "reject", nil, 1), true, false, true) 572 o._putProp("resolve", r.newNativeFunc(r.promise_resolve, nil, "resolve", nil, 1), true, false, true) 573 574 r.putSpeciesReturnThis(o) 575 576 return o 577 } 578 579 func (r *Runtime) initPromise() { 580 r.global.PromisePrototype = r.newLazyObject(r.createPromiseProto) 581 r.global.Promise = r.newLazyObject(r.createPromise) 582 583 r.addToGlobal("Promise", r.global.Promise) 584 } 585 586 func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) { 587 f, _ := AssertFunction(fObj) 588 return func(x interface{}) { 589 _, _ = f(nil, r.ToValue(x)) 590 } 591 } 592 593 // NewPromise creates and returns a Promise and resolving functions for it. 594 // 595 // WARNING: The returned values are not goroutine-safe and must not be called in parallel with VM running. 596 // In order to make use of this method you need an event loop such as the one in goja_nodejs (https://github.com/nuvolaris/goja_nodejs) 597 // where it can be used like this: 598 // 599 // loop := NewEventLoop() 600 // loop.Start() 601 // defer loop.Stop() 602 // loop.RunOnLoop(func(vm *goja.Runtime) { 603 // p, resolve, _ := vm.NewPromise() 604 // vm.Set("p", p) 605 // go func() { 606 // time.Sleep(500 * time.Millisecond) // or perform any other blocking operation 607 // loop.RunOnLoop(func(*goja.Runtime) { // resolve() must be called on the loop, cannot call it here 608 // resolve(result) 609 // }) 610 // }() 611 // } 612 func (r *Runtime) NewPromise() (promise *Promise, resolve func(result interface{}), reject func(reason interface{})) { 613 p := r.newPromise(r.global.PromisePrototype) 614 resolveF, rejectF := p.createResolvingFunctions() 615 return p, r.wrapPromiseReaction(resolveF), r.wrapPromiseReaction(rejectF) 616 } 617 618 // SetPromiseRejectionTracker registers a function that will be called in two scenarios: when a promise is rejected 619 // without any handlers (with operation argument set to PromiseRejectionReject), and when a handler is added to a 620 // rejected promise for the first time (with operation argument set to PromiseRejectionHandle). 621 // 622 // Setting a tracker replaces any existing one. Setting it to nil disables the functionality. 623 // 624 // See https://tc39.es/ecma262/#sec-host-promise-rejection-tracker for more details. 625 func (r *Runtime) SetPromiseRejectionTracker(tracker PromiseRejectionTracker) { 626 r.promiseRejectionTracker = tracker 627 } 628 629 // SetAsyncContextTracker registers a handler that allows to track async execution contexts. See AsyncContextTracker 630 // documentation for more details. Setting it to nil disables the functionality. 631 // This method (as Runtime in general) is not goroutine-safe. 632 func (r *Runtime) SetAsyncContextTracker(tracker AsyncContextTracker) { 633 r.asyncContextTracker = tracker 634 }