Quellcode durchsuchen

Implemented Promise. Closes #178.

Dmitry Panov vor 4 Jahren
Ursprung
Commit
dc8c55024d
12 geänderte Dateien mit 797 neuen und 55 gelöschten Zeilen
  1. 1 5
      builtin_array.go
  2. 26 0
      builtin_error.go
  3. 1 5
      builtin_map.go
  4. 597 0
      builtin_promise.go
  5. 1 5
      builtin_regexp.go
  6. 1 5
      builtin_set.go
  7. 4 15
      builtin_typedarrays.go
  8. 2 0
      object.go
  9. 58 9
      runtime.go
  10. 40 0
      runtime_test.go
  11. 1 0
      string.go
  12. 65 11
      tc39_test.go

+ 1 - 5
builtin_array.go

@@ -1354,11 +1354,7 @@ func (r *Runtime) createArray(val *Object) objectImpl {
 	o._putProp("from", r.newNativeFunc(r.array_from, nil, "from", nil, 1), true, false, true)
 	o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true)
 	o._putProp("of", r.newNativeFunc(r.array_of, nil, "of", nil, 0), true, false, true)
-	o._putSym(SymSpecies, &valueProperty{
-		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
-		accessor:     true,
-		configurable: true,
-	})
+	r.putSpeciesReturnThis(o)
 
 	return o
 }

+ 26 - 0
builtin_error.go

@@ -1,5 +1,27 @@
 package goja
 
+func (r *Runtime) builtin_Error(args []Value, proto *Object) *Object {
+	obj := r.newBaseObject(proto, classError)
+	if len(args) > 0 && args[0] != _undefined {
+		obj._putProp("message", args[0], true, false, true)
+	}
+	return obj.val
+}
+
+func (r *Runtime) builtin_AggregateError(args []Value, proto *Object) *Object {
+	obj := r.newBaseObject(proto, classAggError)
+	if len(args) > 1 && args[1] != _undefined {
+		obj._putProp("message", args[1], true, false, true)
+	}
+	var errors []Value
+	if len(args) > 0 {
+		errors = r.iterableToList(args[0], nil)
+	}
+	obj._putProp("errors", r.newArrayValues(errors), true, false, true)
+
+	return obj.val
+}
+
 func (r *Runtime) createErrorPrototype(name valueString) *Object {
 	o := r.newBaseObject(r.global.ErrorPrototype, classObject)
 	o._putProp("message", stringEmpty, true, false, true)
@@ -17,6 +39,10 @@ func (r *Runtime) initErrors() {
 	r.global.Error = r.newNativeFuncConstruct(r.builtin_Error, "Error", r.global.ErrorPrototype, 1)
 	r.addToGlobal("Error", r.global.Error)
 
+	r.global.AggregateErrorPrototype = r.createErrorPrototype(stringAggregateError)
+	r.global.AggregateError = r.newNativeFuncConstructProto(r.builtin_AggregateError, "AggregateError", r.global.AggregateErrorPrototype, r.global.Error, 1)
+	r.addToGlobal("AggregateError", r.global.AggregateError)
+
 	r.global.TypeErrorPrototype = r.createErrorPrototype(stringTypeError)
 
 	r.global.TypeError = r.newNativeFuncConstructProto(r.builtin_Error, "TypeError", r.global.TypeErrorPrototype, r.global.Error, 1)

+ 1 - 5
builtin_map.go

@@ -243,11 +243,7 @@ func (r *Runtime) createMapProto(val *Object) objectImpl {
 
 func (r *Runtime) createMap(val *Object) objectImpl {
 	o := r.newNativeConstructOnly(val, r.builtin_newMap, r.global.MapPrototype, "Map", 0)
-	o._putSym(SymSpecies, &valueProperty{
-		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
-		accessor:     true,
-		configurable: true,
-	})
+	r.putSpeciesReturnThis(o)
 
 	return o
 }

+ 597 - 0
builtin_promise.go

@@ -0,0 +1,597 @@
+package goja
+
+import (
+	"github.com/dop251/goja/unistring"
+)
+
+type PromiseState int
+type PromiseRejectionOperation int
+
+type promiseReactionType int
+
+const (
+	PromiseStatePending PromiseState = iota
+	PromiseStateFulfilled
+	PromiseStateRejected
+)
+
+const (
+	PromiseRejectionReject PromiseRejectionOperation = iota
+	PromiseRejectionHandle
+)
+
+const (
+	promiseReactionFulfill promiseReactionType = iota
+	promiseReactionReject
+)
+
+type PromiseRejectionTracker func(p *Promise, operation PromiseRejectionOperation)
+
+type jobCallback struct {
+	callback func(FunctionCall) Value
+}
+
+type promiseCapability struct {
+	promise               *Object
+	resolveObj, rejectObj *Object
+}
+
+type promiseReaction struct {
+	capability *promiseCapability
+	typ        promiseReactionType
+	handler    *jobCallback
+}
+
+// Promise is a Go wrapper around ECMAScript Promise. Calling Runtime.ToValue() on it
+// returns the underlying Object. Calling Export() on a Promise Object returns a Promise.
+//
+// Use Runtime.NewPromise() to create one. Calling Runtime.ToValue() on a zero object or nil returns null Value.
+//
+// WARNING: Instances of Promise are not goroutine-safe. See Runtime.NewPromise() for more details.
+type Promise struct {
+	baseObject
+	state            PromiseState
+	result           Value
+	fulfillReactions []*promiseReaction
+	rejectReactions  []*promiseReaction
+	handled          bool
+}
+
+func (p *Promise) State() PromiseState {
+	return p.state
+}
+
+func (p *Promise) Result() Value {
+	return p.result
+}
+
+func (p *Promise) toValue(r *Runtime) Value {
+	if p == nil || p.val == nil {
+		return _null
+	}
+	promise := p.val
+	if promise.runtime != r {
+		panic(r.NewTypeError("Illegal runtime transition of a Promise"))
+	}
+	return promise
+}
+
+func (p *Promise) createResolvingFunctions() (resolve, reject *Object) {
+	r := p.val.runtime
+	alreadyResolved := false
+	return p.val.runtime.newNativeFunc(func(call FunctionCall) Value {
+			if alreadyResolved {
+				return _undefined
+			}
+			alreadyResolved = true
+			resolution := call.Argument(0)
+			if resolution.SameAs(p.val) {
+				return p.reject(r.NewTypeError("Promise self-resolution"))
+			}
+			if obj, ok := resolution.(*Object); ok {
+				var thenAction Value
+				ex := r.vm.try(func() {
+					thenAction = obj.self.getStr("then", nil)
+				})
+				if ex != nil {
+					return p.reject(ex.val)
+				}
+				if call, ok := assertCallable(thenAction); ok {
+					job := r.newPromiseResolveThenableJob(p, resolution, &jobCallback{callback: call})
+					r.enqueuePromiseJob(job)
+					return _undefined
+				}
+			}
+			return p.fulfill(resolution)
+		}, nil, "", nil, 1),
+		p.val.runtime.newNativeFunc(func(call FunctionCall) Value {
+			if alreadyResolved {
+				return _undefined
+			}
+			alreadyResolved = true
+			reason := call.Argument(0)
+			return p.reject(reason)
+		}, nil, "", nil, 1)
+}
+
+func (p *Promise) reject(reason Value) Value {
+	reactions := p.rejectReactions
+	p.result = reason
+	p.fulfillReactions, p.rejectReactions = nil, nil
+	p.state = PromiseStateRejected
+	r := p.val.runtime
+	if !p.handled {
+		r.trackPromiseRejection(p, PromiseRejectionReject)
+	}
+	r.triggerPromiseReactions(reactions, reason)
+	return _undefined
+}
+
+func (p *Promise) fulfill(value Value) Value {
+	reactions := p.fulfillReactions
+	p.result = value
+	p.fulfillReactions, p.rejectReactions = nil, nil
+	p.state = PromiseStateFulfilled
+	p.val.runtime.triggerPromiseReactions(reactions, value)
+	return _undefined
+}
+
+func (r *Runtime) newPromiseResolveThenableJob(p *Promise, thenable Value, then *jobCallback) func() {
+	return func() {
+		resolve, reject := p.createResolvingFunctions()
+		ex := r.vm.try(func() {
+			r.callJobCallback(then, thenable, resolve, reject)
+		})
+		if ex != nil {
+			if fn, ok := reject.self.assertCallable(); ok {
+				fn(FunctionCall{Arguments: []Value{ex.val}})
+			}
+		}
+	}
+}
+
+func (r *Runtime) enqueuePromiseJob(job func()) {
+	r.jobQueue = append(r.jobQueue, job)
+}
+
+func (r *Runtime) triggerPromiseReactions(reactions []*promiseReaction, argument Value) {
+	for _, reaction := range reactions {
+		r.enqueuePromiseJob(r.newPromiseReactionJob(reaction, argument))
+	}
+}
+
+func (r *Runtime) newPromiseReactionJob(reaction *promiseReaction, argument Value) func() {
+	return func() {
+		var handlerResult Value
+		fulfill := false
+		if reaction.handler == nil {
+			handlerResult = argument
+			if reaction.typ == promiseReactionFulfill {
+				fulfill = true
+			}
+		} else {
+			ex := r.vm.try(func() {
+				handlerResult = r.callJobCallback(reaction.handler, _undefined, argument)
+				fulfill = true
+			})
+			if ex != nil {
+				handlerResult = ex.val
+			}
+		}
+		if reaction.capability != nil {
+			if fulfill {
+				reaction.capability.resolve(handlerResult)
+			} else {
+				reaction.capability.reject(handlerResult)
+			}
+		}
+	}
+}
+
+func (r *Runtime) newPromise(proto *Object) *Promise {
+	o := &Object{runtime: r}
+
+	po := &Promise{}
+	po.class = classPromise
+	po.val = o
+	po.extensible = true
+	o.self = po
+	po.prototype = proto
+	po.init()
+	return po
+}
+
+func (r *Runtime) builtin_newPromise(args []Value, newTarget *Object) *Object {
+	if newTarget == nil {
+		panic(r.needNew("Promise"))
+	}
+	var arg0 Value
+	if len(args) > 0 {
+		arg0 = args[0]
+	}
+	executor := r.toCallable(arg0)
+
+	proto := r.getPrototypeFromCtor(newTarget, r.global.Promise, r.global.PromisePrototype)
+	po := r.newPromise(proto)
+
+	resolve, reject := po.createResolvingFunctions()
+	ex := r.vm.try(func() {
+		executor(FunctionCall{Arguments: []Value{resolve, reject}})
+	})
+	if ex != nil {
+		if fn, ok := reject.self.assertCallable(); ok {
+			fn(FunctionCall{Arguments: []Value{ex.val}})
+		}
+	}
+	return po.val
+}
+
+func (r *Runtime) promiseProto_then(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	if p, ok := thisObj.self.(*Promise); ok {
+		c := r.speciesConstructorObj(thisObj, r.global.Promise)
+		resultCapability := r.newPromiseCapability(c)
+		return r.performPromiseThen(p, call.Argument(0), call.Argument(1), resultCapability)
+	}
+	panic(r.NewTypeError("Method Promise.prototype.then called on incompatible receiver %s", thisObj))
+}
+
+func (r *Runtime) newPromiseCapability(c *Object) *promiseCapability {
+	pcap := new(promiseCapability)
+	if c == r.global.Promise {
+		p := r.newPromise(r.global.PromisePrototype)
+		pcap.resolveObj, pcap.rejectObj = p.createResolvingFunctions()
+		pcap.promise = p.val
+	} else {
+		var resolve, reject Value
+		executor := r.newNativeFunc(func(call FunctionCall) Value {
+			if resolve != nil {
+				panic(r.NewTypeError("resolve is already set"))
+			}
+			if reject != nil {
+				panic(r.NewTypeError("reject is already set"))
+			}
+			if arg := call.Argument(0); arg != _undefined {
+				resolve = arg
+			}
+			if arg := call.Argument(1); arg != _undefined {
+				reject = arg
+			}
+			return nil
+		}, nil, "", nil, 2)
+		pcap.promise = r.toConstructor(c)([]Value{executor}, c)
+		pcap.resolveObj = r.toObject(resolve)
+		r.toCallable(pcap.resolveObj) // make sure it's callable
+		pcap.rejectObj = r.toObject(reject)
+		r.toCallable(pcap.rejectObj)
+	}
+	return pcap
+}
+
+func (r *Runtime) performPromiseThen(p *Promise, onFulfilled, onRejected Value, resultCapability *promiseCapability) Value {
+	var onFulfilledJobCallback, onRejectedJobCallback *jobCallback
+	if f, ok := assertCallable(onFulfilled); ok {
+		onFulfilledJobCallback = &jobCallback{callback: f}
+	}
+	if f, ok := assertCallable(onRejected); ok {
+		onRejectedJobCallback = &jobCallback{callback: f}
+	}
+	fulfillReaction := &promiseReaction{
+		capability: resultCapability,
+		typ:        promiseReactionFulfill,
+		handler:    onFulfilledJobCallback,
+	}
+	rejectReaction := &promiseReaction{
+		capability: resultCapability,
+		typ:        promiseReactionReject,
+		handler:    onRejectedJobCallback,
+	}
+	switch p.state {
+	case PromiseStatePending:
+		p.fulfillReactions = append(p.fulfillReactions, fulfillReaction)
+		p.rejectReactions = append(p.rejectReactions, rejectReaction)
+	case PromiseStateFulfilled:
+		r.enqueuePromiseJob(r.newPromiseReactionJob(fulfillReaction, p.result))
+	default:
+		reason := p.result
+		if !p.handled {
+			r.trackPromiseRejection(p, PromiseRejectionHandle)
+		}
+		r.enqueuePromiseJob(r.newPromiseReactionJob(rejectReaction, reason))
+	}
+	p.handled = true
+	if resultCapability == nil {
+		return _undefined
+	}
+	return resultCapability.promise
+}
+
+func (r *Runtime) promiseProto_catch(call FunctionCall) Value {
+	return r.invoke(call.This, "then", _undefined, call.Argument(0))
+}
+
+func (r *Runtime) promiseResolve(c *Object, x Value) *Object {
+	if obj, ok := x.(*Object); ok {
+		xConstructor := nilSafe(obj.self.getStr("constructor", nil))
+		if xConstructor.SameAs(c) {
+			return obj
+		}
+	}
+	pcap := r.newPromiseCapability(c)
+	pcap.resolve(x)
+	return pcap.promise
+}
+
+func (r *Runtime) promiseProto_finally(call FunctionCall) Value {
+	promise := r.toObject(call.This)
+	c := r.speciesConstructorObj(promise, r.global.Promise)
+	onFinally := call.Argument(0)
+	var thenFinally, catchFinally Value
+	if onFinallyFn, ok := assertCallable(onFinally); !ok {
+		thenFinally, catchFinally = onFinally, onFinally
+	} else {
+		thenFinally = r.newNativeFunc(func(call FunctionCall) Value {
+			value := call.Argument(0)
+			result := onFinallyFn(FunctionCall{})
+			promise := r.promiseResolve(c, result)
+			valueThunk := r.newNativeFunc(func(call FunctionCall) Value {
+				return value
+			}, nil, "", nil, 0)
+			return r.invoke(promise, "then", valueThunk)
+		}, nil, "", nil, 1)
+
+		catchFinally = r.newNativeFunc(func(call FunctionCall) Value {
+			reason := call.Argument(0)
+			result := onFinallyFn(FunctionCall{})
+			promise := r.promiseResolve(c, result)
+			thrower := r.newNativeFunc(func(call FunctionCall) Value {
+				panic(reason)
+			}, nil, "", nil, 0)
+			return r.invoke(promise, "then", thrower)
+		}, nil, "", nil, 1)
+	}
+	return r.invoke(promise, "then", thenFinally, catchFinally)
+}
+
+func (pcap *promiseCapability) resolve(result Value) {
+	pcap.promise.runtime.toCallable(pcap.resolveObj)(FunctionCall{Arguments: []Value{result}})
+}
+
+func (pcap *promiseCapability) reject(reason Value) {
+	pcap.promise.runtime.toCallable(pcap.rejectObj)(FunctionCall{Arguments: []Value{reason}})
+}
+
+func (pcap *promiseCapability) try(f func()) bool {
+	ex := pcap.promise.runtime.vm.try(f)
+	if ex != nil {
+		pcap.reject(ex.val)
+		return false
+	}
+	return true
+}
+
+func (r *Runtime) promise_all(call FunctionCall) Value {
+	c := r.toObject(call.This)
+	pcap := r.newPromiseCapability(c)
+
+	pcap.try(func() {
+		promiseResolve := r.toCallable(c.self.getStr("resolve", nil))
+		iter := r.getIterator(call.Argument(0), nil)
+		var values []Value
+		remainingElementsCount := 1
+		r.iterate(iter, func(nextValue Value) {
+			index := len(values)
+			values = append(values, _undefined)
+			nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}})
+			alreadyCalled := false
+			onFulfilled := r.newNativeFunc(func(call FunctionCall) Value {
+				if alreadyCalled {
+					return _undefined
+				}
+				alreadyCalled = true
+				values[index] = call.Argument(0)
+				remainingElementsCount--
+				if remainingElementsCount == 0 {
+					pcap.resolve(r.newArrayValues(values))
+				}
+				return _undefined
+			}, nil, "", nil, 1)
+			remainingElementsCount++
+			r.invoke(nextPromise, "then", onFulfilled, pcap.rejectObj)
+		})
+		remainingElementsCount--
+		if remainingElementsCount == 0 {
+			pcap.resolve(r.newArrayValues(values))
+		}
+	})
+	return pcap.promise
+}
+
+func (r *Runtime) promise_allSettled(call FunctionCall) Value {
+	c := r.toObject(call.This)
+	pcap := r.newPromiseCapability(c)
+
+	pcap.try(func() {
+		promiseResolve := r.toCallable(c.self.getStr("resolve", nil))
+		iter := r.getIterator(call.Argument(0), nil)
+		var values []Value
+		remainingElementsCount := 1
+		r.iterate(iter, func(nextValue Value) {
+			index := len(values)
+			values = append(values, _undefined)
+			nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}})
+			alreadyCalled := false
+			reaction := func(status Value, valueKey unistring.String) *Object {
+				return r.newNativeFunc(func(call FunctionCall) Value {
+					if alreadyCalled {
+						return _undefined
+					}
+					alreadyCalled = true
+					obj := r.NewObject()
+					obj.self._putProp("status", status, true, true, true)
+					obj.self._putProp(valueKey, call.Argument(0), true, true, true)
+					values[index] = obj
+					remainingElementsCount--
+					if remainingElementsCount == 0 {
+						pcap.resolve(r.newArrayValues(values))
+					}
+					return _undefined
+				}, nil, "", nil, 1)
+			}
+			onFulfilled := reaction(asciiString("fulfilled"), "value")
+			onRejected := reaction(asciiString("rejected"), "reason")
+			remainingElementsCount++
+			r.invoke(nextPromise, "then", onFulfilled, onRejected)
+		})
+		remainingElementsCount--
+		if remainingElementsCount == 0 {
+			pcap.resolve(r.newArrayValues(values))
+		}
+	})
+	return pcap.promise
+}
+
+func (r *Runtime) promise_any(call FunctionCall) Value {
+	c := r.toObject(call.This)
+	pcap := r.newPromiseCapability(c)
+
+	pcap.try(func() {
+		promiseResolve := r.toCallable(c.self.getStr("resolve", nil))
+		iter := r.getIterator(call.Argument(0), nil)
+		var errors []Value
+		remainingElementsCount := 1
+		r.iterate(iter, func(nextValue Value) {
+			index := len(errors)
+			errors = append(errors, _undefined)
+			nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}})
+			alreadyCalled := false
+			onRejected := r.newNativeFunc(func(call FunctionCall) Value {
+				if alreadyCalled {
+					return _undefined
+				}
+				alreadyCalled = true
+				errors[index] = call.Argument(0)
+				remainingElementsCount--
+				if remainingElementsCount == 0 {
+					_error := r.builtin_new(r.global.AggregateError, nil)
+					_error.self._putProp("errors", r.newArrayValues(errors), true, false, true)
+					pcap.reject(_error)
+				}
+				return _undefined
+			}, nil, "", nil, 1)
+
+			remainingElementsCount++
+			r.invoke(nextPromise, "then", pcap.resolveObj, onRejected)
+		})
+		remainingElementsCount--
+		if remainingElementsCount == 0 {
+			_error := r.builtin_new(r.global.AggregateError, nil)
+			_error.self._putProp("errors", r.newArrayValues(errors), true, false, true)
+			pcap.reject(_error)
+		}
+	})
+	return pcap.promise
+}
+
+func (r *Runtime) promise_race(call FunctionCall) Value {
+	c := r.toObject(call.This)
+	pcap := r.newPromiseCapability(c)
+
+	pcap.try(func() {
+		promiseResolve := r.toCallable(c.self.getStr("resolve", nil))
+		iter := r.getIterator(call.Argument(0), nil)
+		r.iterate(iter, func(nextValue Value) {
+			nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}})
+			r.invoke(nextPromise, "then", pcap.resolveObj, pcap.rejectObj)
+		})
+	})
+	return pcap.promise
+}
+
+func (r *Runtime) promise_reject(call FunctionCall) Value {
+	pcap := r.newPromiseCapability(r.toObject(call.This))
+	pcap.reject(call.Argument(0))
+	return pcap.promise
+}
+
+func (r *Runtime) promise_resolve(call FunctionCall) Value {
+	return r.promiseResolve(r.toObject(call.This), call.Argument(0))
+}
+
+func (r *Runtime) createPromiseProto(val *Object) objectImpl {
+	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
+	o._putProp("constructor", r.global.Promise, true, false, true)
+
+	o._putProp("catch", r.newNativeFunc(r.promiseProto_catch, nil, "catch", nil, 1), true, false, true)
+	o._putProp("finally", r.newNativeFunc(r.promiseProto_finally, nil, "finally", nil, 1), true, false, true)
+	o._putProp("then", r.newNativeFunc(r.promiseProto_then, nil, "then", nil, 2), true, false, true)
+
+	o._putSym(SymToStringTag, valueProp(asciiString(classPromise), false, false, true))
+
+	return o
+}
+
+func (r *Runtime) createPromise(val *Object) objectImpl {
+	o := r.newNativeConstructOnly(val, r.builtin_newPromise, r.global.PromisePrototype, "Promise", 1)
+
+	o._putProp("all", r.newNativeFunc(r.promise_all, nil, "all", nil, 1), true, false, true)
+	o._putProp("allSettled", r.newNativeFunc(r.promise_allSettled, nil, "allSettled", nil, 1), true, false, true)
+	o._putProp("any", r.newNativeFunc(r.promise_any, nil, "any", nil, 1), true, false, true)
+	o._putProp("race", r.newNativeFunc(r.promise_race, nil, "race", nil, 1), true, false, true)
+	o._putProp("reject", r.newNativeFunc(r.promise_reject, nil, "reject", nil, 1), true, false, true)
+	o._putProp("resolve", r.newNativeFunc(r.promise_resolve, nil, "resolve", nil, 1), true, false, true)
+
+	r.putSpeciesReturnThis(o)
+
+	return o
+}
+
+func (r *Runtime) initPromise() {
+	r.global.PromisePrototype = r.newLazyObject(r.createPromiseProto)
+	r.global.Promise = r.newLazyObject(r.createPromise)
+
+	r.addToGlobal("Promise", r.global.Promise)
+}
+
+func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) {
+	f, _ := AssertFunction(fObj)
+	return func(x interface{}) {
+		_, _ = f(nil, r.ToValue(x))
+	}
+}
+
+// NewPromise creates and returns a Promise and resolving functions for it.
+//
+// WARNING: The returned values are not goroutine-safe and must not be called in parallel with VM running.
+// 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)
+// where it can be used like this:
+//
+//  loop := NewEventLoop()
+//  loop.Start()
+//  defer loop.Stop()
+//  loop.RunOnLoop(func(vm *goja.Runtime) {
+//		p, resolve, _ := vm.NewPromise()
+//		vm.Set("p", p)
+//      go func() {
+//   		time.Sleep(500 * time.Millisecond)   // or perform any other blocking operation
+//			loop.RunOnLoop(func(*goja.Runtime) { // resolve() must be called on the loop, cannot call it here
+//				resolve(result)
+//			})
+//		}()
+//  }
+func (r *Runtime) NewPromise() (promise *Promise, resolve func(result interface{}), reject func(reason interface{})) {
+	p := r.newPromise(r.global.PromisePrototype)
+	resolveF, rejectF := p.createResolvingFunctions()
+	return p, r.wrapPromiseReaction(resolveF), r.wrapPromiseReaction(rejectF)
+}
+
+// SetPromiseRejectionTracker registers a function that will be called in two scenarios: when a promise is rejected
+// without any handlers (with operation argument set to PromiseRejectionReject), and when a handler is added to a
+// rejected promise for the first time (with operation argument set to PromiseRejectionHandle).
+//
+// Setting a tracker replaces any existing one. Setting it to nil disables the functionality.
+//
+// See https://tc39.es/ecma262/#sec-host-promise-rejection-tracker for more details.
+func (r *Runtime) SetPromiseRejectionTracker(tracker PromiseRejectionTracker) {
+	r.promiseRejectionTracker = tracker
+}

+ 1 - 5
builtin_regexp.go

@@ -1267,10 +1267,6 @@ func (r *Runtime) initRegExp() {
 
 	r.global.RegExp = r.newNativeFunc(r.builtin_RegExp, r.builtin_newRegExp, "RegExp", r.global.RegExpPrototype, 2)
 	rx := r.global.RegExp.self
-	rx._putSym(SymSpecies, &valueProperty{
-		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
-		accessor:     true,
-		configurable: true,
-	})
+	r.putSpeciesReturnThis(rx)
 	r.addToGlobal("RegExp", r.global.RegExp)
 }

+ 1 - 5
builtin_set.go

@@ -218,11 +218,7 @@ func (r *Runtime) createSetProto(val *Object) objectImpl {
 
 func (r *Runtime) createSet(val *Object) objectImpl {
 	o := r.newNativeConstructOnly(val, r.builtin_newSet, r.global.SetPrototype, "Set", 0)
-	o._putSym(SymSpecies, &valueProperty{
-		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
-		accessor:     true,
-		configurable: true,
-	})
+	r.putSpeciesReturnThis(o)
 
 	return o
 }

+ 4 - 15
builtin_typedarrays.go

@@ -1094,11 +1094,7 @@ func (r *Runtime) typedArrayFrom(ctor, items *Object, mapFn, thisValue Value) *O
 	}
 	usingIter := toMethod(items.self.getSym(SymIterator, nil))
 	if usingIter != nil {
-		iter := r.getIterator(items, usingIter)
-		var values []Value
-		r.iterate(iter, func(item Value) {
-			values = append(values, item)
-		})
+		values := r.iterableToList(items, usingIter)
 		ta := r.typedArrayCreate(ctor, []Value{intToValue(int64(len(values)))})
 		if mapFc == nil {
 			for idx, val := range values {
@@ -1260,11 +1256,8 @@ func (r *Runtime) createArrayBufferProto(val *Object) objectImpl {
 func (r *Runtime) createArrayBuffer(val *Object) objectImpl {
 	o := r.newNativeConstructOnly(val, r.builtin_newArrayBuffer, r.global.ArrayBufferPrototype, "ArrayBuffer", 1)
 	o._putProp("isView", r.newNativeFunc(r.arrayBuffer_isView, nil, "isView", nil, 1), true, false, true)
-	o._putSym(SymSpecies, &valueProperty{
-		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
-		accessor:     true,
-		configurable: true,
-	})
+	r.putSpeciesReturnThis(o)
+
 	return o
 }
 
@@ -1375,11 +1368,7 @@ func (r *Runtime) createTypedArray(val *Object) objectImpl {
 	o := r.newNativeConstructOnly(val, r.newTypedArray, r.global.TypedArrayPrototype, "TypedArray", 0)
 	o._putProp("from", r.newNativeFunc(r.typedArray_from, nil, "from", nil, 1), true, false, true)
 	o._putProp("of", r.newNativeFunc(r.typedArray_of, nil, "of", nil, 0), true, false, true)
-	o._putSym(SymSpecies, &valueProperty{
-		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
-		accessor:     true,
-		configurable: true,
-	})
+	r.putSpeciesReturnThis(o)
 
 	return o
 }

+ 2 - 0
object.go

@@ -22,10 +22,12 @@ const (
 	classString   = "String"
 	classBoolean  = "Boolean"
 	classError    = "Error"
+	classAggError = "AggregateError"
 	classRegExp   = "RegExp"
 	classDate     = "Date"
 	classJSON     = "JSON"
 	classGlobal   = "global"
+	classPromise  = "Promise"
 
 	classArrayIterator        = "Array Iterator"
 	classMapIterator          = "Map Iterator"

+ 58 - 9
runtime.go

@@ -58,6 +58,7 @@ type global struct {
 	Date     *Object
 	Symbol   *Object
 	Proxy    *Object
+	Promise  *Object
 
 	ArrayBuffer       *Object
 	DataView          *Object
@@ -78,6 +79,7 @@ type global struct {
 	Set     *Object
 
 	Error          *Object
+	AggregateError *Object
 	TypeError      *Object
 	ReferenceError *Object
 	SyntaxError    *Object
@@ -104,6 +106,7 @@ type global struct {
 	WeakMapPrototype     *Object
 	MapPrototype         *Object
 	SetPrototype         *Object
+	PromisePrototype     *Object
 
 	IteratorPrototype             *Object
 	ArrayIteratorPrototype        *Object
@@ -113,6 +116,7 @@ type global struct {
 	RegExpStringIteratorPrototype *Object
 
 	ErrorPrototype          *Object
+	AggregateErrorPrototype *Object
 	TypeErrorPrototype      *Object
 	SyntaxErrorPrototype    *Object
 	RangeErrorPrototype     *Object
@@ -177,6 +181,10 @@ type Runtime struct {
 	vm    *vm
 	hash  *maphash.Hash
 	idSeq uint64
+
+	jobQueue []func()
+
+	promiseRejectionTracker PromiseRejectionTracker
 }
 
 type StackFrame struct {
@@ -387,6 +395,7 @@ func (r *Runtime) init() {
 	r.initWeakMap()
 	r.initMap()
 	r.initSet()
+	r.initPromise()
 
 	r.global.thrower = r.newNativeFunc(r.builtin_thrower, nil, "thrower", nil, 0)
 	r.global.throwerProperty = &valueProperty{
@@ -776,14 +785,6 @@ func (r *Runtime) error_toString(call FunctionCall) Value {
 	return sb.String()
 }
 
-func (r *Runtime) builtin_Error(args []Value, proto *Object) *Object {
-	obj := r.newBaseObject(proto, classError)
-	if len(args) > 0 && args[0] != _undefined {
-		obj._putProp("message", args[0], true, false, true)
-	}
-	return obj.val
-}
-
 func (r *Runtime) builtin_new(construct *Object, args []Value) *Object {
 	return r.toConstructor(construct)(args, nil)
 }
@@ -2395,7 +2396,16 @@ func (r *Runtime) getHash() *maphash.Hash {
 
 // called when the top level function returns (i.e. control is passed outside the Runtime).
 func (r *Runtime) leave() {
-	// run jobs, etc...
+	for {
+		jobs := r.jobQueue
+		r.jobQueue = nil
+		if len(jobs) == 0 {
+			break
+		}
+		for _, job := range jobs {
+			job()
+		}
+	}
 }
 
 func nilSafe(v Value) Value {
@@ -2505,6 +2515,38 @@ func (r *Runtime) setGlobal(name unistring.String, v Value, strict bool) {
 	}
 }
 
+func (r *Runtime) trackPromiseRejection(p *Promise, operation PromiseRejectionOperation) {
+	if r.promiseRejectionTracker != nil {
+		r.promiseRejectionTracker(p, operation)
+	}
+}
+
+func (r *Runtime) callJobCallback(job *jobCallback, this Value, args ...Value) Value {
+	return job.callback(FunctionCall{This: this, Arguments: args})
+}
+
+func (r *Runtime) invoke(v Value, p unistring.String, args ...Value) Value {
+	o := v.ToObject(r)
+	return r.toCallable(o.self.getStr(p, nil))(FunctionCall{This: v, Arguments: args})
+}
+
+func (r *Runtime) iterableToList(items Value, method func(FunctionCall) Value) []Value {
+	iter := r.getIterator(items, method)
+	var values []Value
+	r.iterate(iter, func(item Value) {
+		values = append(values, item)
+	})
+	return values
+}
+
+func (r *Runtime) putSpeciesReturnThis(o objectImpl) {
+	o._putSym(SymSpecies, &valueProperty{
+		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
+		accessor:     true,
+		configurable: true,
+	})
+}
+
 func strToArrayIdx(s unistring.String) uint32 {
 	if s == "" {
 		return math.MaxUint32
@@ -2716,3 +2758,10 @@ func strToIdx64(s unistring.String) int64 {
 	}
 	return -1
 }
+
+func assertCallable(v Value) (func(FunctionCall) Value, bool) {
+	if obj, ok := v.(*Object); ok {
+		return obj.self.assertCallable()
+	}
+	return nil, false
+}

+ 40 - 0
runtime_test.go

@@ -2275,6 +2275,46 @@ func TestStringToBytesConversion(t *testing.T) {
 	}
 }
 
+func TestPromiseAll(t *testing.T) {
+	const SCRIPT = `
+var p1 = new Promise(function() {});
+var p2 = new Promise(function() {});
+var p3 = new Promise(function() {});
+var callCount = 0;
+var currentThis = p1;
+var nextThis = p2;
+var afterNextThis = p3;
+
+p1.then = p2.then = p3.then = function(a, b) {
+  assert.sameValue(typeof a, 'function', 'type of first argument');
+  assert.sameValue(
+    a.length,
+    1,
+    'ES6 25.4.1.3.2: The length property of a promise resolve function is 1.'
+  );
+  assert.sameValue(typeof b, 'function', 'type of second argument');
+  assert.sameValue(
+    b.length,
+    1,
+    'ES6 25.4.1.3.1: The length property of a promise reject function is 1.'
+  );
+  assert.sameValue(arguments.length, 2, '"then"" invoked with two arguments');
+  assert.sameValue(this, currentThis, '"this" value');
+
+  currentThis = nextThis;
+  nextThis = afterNextThis;
+  afterNextThis = null;
+
+  callCount += 1;
+};
+
+Promise.all([p1, p2, p3]);
+
+assert.sameValue(callCount, 3, '"then"" invoked once for every iterated value');
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
 /*
 func TestArrayConcatSparse(t *testing.T) {
 function foo(a,b,c)

+ 1 - 0
string.go

@@ -33,6 +33,7 @@ var (
 	stringEmpty        valueString = asciiString("")
 
 	stringError          valueString = asciiString("Error")
+	stringAggregateError valueString = asciiString("AggregateError")
 	stringTypeError      valueString = asciiString("TypeError")
 	stringReferenceError valueString = asciiString("ReferenceError")
 	stringSyntaxError    valueString = asciiString("SyntaxError")

+ 65 - 11
tc39_test.go

@@ -77,13 +77,24 @@ var (
 		// 167e596a649ede35df11d03cb3c093941c9cf396
 		"test/built-ins/TypedArrayConstructors/internals/Set/detached-buffer.js": true,
 
-		// 59a1a016b7cf5cf43f66b274c7d1db4ec6066935
-		"test/language/expressions/function/name.js":                 true,
-		"test/built-ins/Proxy/revocable/revocation-function-name.js": true,
-
-		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js":  true, // timezone
-		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js":  true, // timezone
-		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone
+		// Anonymous function name property (now always present)
+		"test/language/expressions/function/name.js":                         true,
+		"test/built-ins/Proxy/revocable/revocation-function-name.js":         true,
+		"test/built-ins/Promise/all/resolve-element-function-name.js":        true,
+		"test/built-ins/Promise/allSettled/resolve-element-function-name.js": true,
+		"test/built-ins/Promise/allSettled/reject-element-function-name.js":  true,
+		"test/built-ins/Promise/resolve-function-name.js":                    true,
+		"test/built-ins/Promise/reject-function-name.js":                     true,
+
+		// obsolete tests (to remove)
+		"test/built-ins/Promise/race/invoke-resolve-get-error-close.js":       true,
+		"test/built-ins/Promise/allSettled/invoke-resolve-get-error-close.js": true,
+		"test/built-ins/Promise/all/invoke-resolve-get-error-close.js":        true,
+
+		// timezone
+		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js":  true,
+		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js":  true,
+		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true,
 
 		// SharedArrayBuffer
 		"test/built-ins/ArrayBuffer/prototype/slice/this-is-sharedarraybuffer.js": true,
@@ -222,6 +233,14 @@ var (
 		"test/language/expressions/arrow-function/lexical-super-property-from-within-constructor.js":                 true,
 		"test/language/expressions/arrow-function/lexical-super-property.js":                                         true,
 		"test/language/expressions/arrow-function/lexical-supercall-from-immediately-invoked-arrow.js":               true,
+		"test/language/statements/class/subclass/builtin-objects/Promise/super-must-be-called.js":                    true,
+		"test/language/statements/class/subclass/builtin-objects/Promise/regular-subclassing.js":                     true,
+		"test/built-ins/Promise/prototype/finally/subclass-species-constructor-resolve-count.js":                     true,
+		"test/built-ins/Promise/prototype/finally/subclass-species-constructor-reject-count.js":                      true,
+		"test/built-ins/Promise/prototype/finally/subclass-resolve-count.js":                                         true,
+		"test/built-ins/Promise/prototype/finally/species-symbol.js":                                                 true,
+		"test/built-ins/Promise/prototype/finally/subclass-reject-count.js":                                          true,
+		"test/built-ins/Promise/prototype/finally/species-constructor.js":                                            true,
 
 		// restricted unicode regexp syntax
 		"test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js":         true,
@@ -343,6 +362,7 @@ var (
 		"23",
 		"24",
 		"25.1",
+		"25.4",
 		"26",
 		"B.2.1",
 		"B.2.2",
@@ -410,6 +430,14 @@ var (
 		"sec-literals-numeric-literals",
 		"sec-literals-string-literals",
 		"sec-additional-syntax-numeric-literals",
+		"sec-promise",
+		"sec-promise-constructor",
+		"sec-promise-executor",
+		"sec-promise-reject-functions",
+		"sec-promise-resolve-functions",
+		"sec-performpromiseall",
+		"sec-performpromiseallsettled",
+		"sec-properties-of-the-promise-prototype-object",
 	}
 )
 
@@ -522,8 +550,21 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing.
 	_262.Set("createRealm", ctx.throwIgnorableTestError)
 	vm.Set("$262", _262)
 	vm.Set("IgnorableTestError", ignorableTestError)
-	vm.Set("print", t.Log)
 	vm.RunProgram(sabStub)
+	var out []string
+	async := meta.hasFlag("async")
+	if async {
+		err := ctx.runFile(ctx.base, path.Join("harness", "doneprintHandle.js"), vm)
+		if err != nil {
+			t.Fatal(err)
+		}
+		vm.Set("print", func(msg string) {
+			out = append(out, msg)
+		})
+	} else {
+		vm.Set("print", t.Log)
+	}
+
 	err, early := ctx.runTC39Script(name, src, meta.Includes, vm)
 
 	if err != nil {
@@ -582,6 +623,22 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing.
 	if l := len(vm.vm.iterStack); l > 0 {
 		t.Fatalf("iter stack is not empty: %d", l)
 	}
+	if async {
+		complete := false
+		for _, line := range out {
+			if strings.HasPrefix(line, "Test262:AsyncTestFailure:") {
+				t.Fatal(line)
+			} else if line == "Test262:AsyncTestComplete" {
+				complete = true
+			}
+		}
+		if !complete {
+			for _, line := range out {
+				t.Log(line)
+			}
+			t.Fatal("Test262:AsyncTestComplete was not printed")
+		}
+	}
 }
 
 func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) {
@@ -595,9 +652,6 @@ func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) {
 		t.Errorf("Could not parse %s: %v", name, err)
 		return
 	}
-	if meta.hasFlag("async") {
-		t.Skip("async")
-	}
 	if meta.Es5id == "" {
 		skip := true
 		//t.Logf("%s: Not ES5, skipped", name)