builtin_promise.go 18 KB

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