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
51
52
53
54
55
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)
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
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
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
631
632
633
634
635
636
637 func (r *Runtime) SetPromiseRejectionTracker(tracker PromiseRejectionTracker) {
638 r.promiseRejectionTracker = tracker
639 }
640
641
642
643
644 func (r *Runtime) SetAsyncContextTracker(tracker AsyncContextTracker) {
645 r.asyncContextTracker = tracker
646 }
647
View as plain text