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