GopeedLab/gopeed · error

promise.then is not callable

Error message

promise.then is not callable

What it means

Same broken-thenable check as the main engine, but inside the stream inject module's loop helper (pkg/download/engine/inject/stream/module.go:499-526): a value returned by stream-related script code exports as *goja.Promise, is still pending, and value.ToObject(runtime).Get("then") is not callable (goja.AssertFunction fails). The stream module then aborts the operation with 'promise.then is not callable'.

Source

Thrown at pkg/download/engine/inject/stream/module.go:511

		}()
		value, err := fn(runtime)
		if err != nil {
			send(nil, err)
			return
		}
		if promise, ok := value.Export().(*goja.Promise); ok {
			switch promise.State() {
			case goja.PromiseStateFulfilled:
				send(promise.Result(), nil)
				return
			case goja.PromiseStateRejected:
				send(nil, exportJSError(promise.Result()))
				return
			default:
				thenVal := value.ToObject(runtime).Get("then")
				thenFn, ok := goja.AssertFunction(thenVal)
				if !ok {
					send(nil, errors.New("promise.then is not callable"))
					return
				}
				onFulfilled := runtime.ToValue(func(call goja.FunctionCall) goja.Value {
					send(call.Argument(0), nil)
					return goja.Undefined()
				})
				onRejected := runtime.ToValue(func(call goja.FunctionCall) goja.Value {
					send(nil, exportJSError(call.Argument(0)))
					return goja.Undefined()
				})
				if _, err := thenFn(value, onFulfilled, onRejected); err != nil {
					send(nil, err)
				}
				return
			}
		}
		send(value, nil)
	})

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Return genuine promises from stream script code — async functions or the engine's own fetch/stream APIs
  2. Do not override Promise in scripts run by this engine
  3. If a custom thenable is required, give it a real function then(onFulfilled, onRejected)
  4. Log the offending value (typeof value, Object.keys(value)) from a debug copy of the script to identify what lacks then

Example fix

// before (script)
const res = { then: null, body: stream }
return res

// after (script)
return Promise.resolve(stream)
Defensive patterns

Strategy: try-catch

Validate before calling

// Script-side: return genuine promises from stream code
// return Promise.resolve(streamObj) — never {then: <non-function>}

Type guard

// Script-side:
if (v != null && typeof v === 'object' && typeof v.then !== 'function') {
    v = Promise.resolve(v)
}

Try / catch

if err != nil && strings.Contains(err.Error(), "promise.then is not callable") {
    // stream script returned a broken thenable: fix the script's return value
}

Prevention

When it happens

Trigger: Stream/fetch polyfill code in a resolver script returns a fake thenable ({then: 'not-a-fn'}) or an object masquerading as a promise; the script overrides Promise with a non-standard implementation whose instances lack callable then; a Go-provided stream object is returned raw where a promise was expected, and its 'then' JSON field is not a function.

Common situations: Resolver scripts replacing or monkey-patching globalThis.Promise; returning the stream object from an async wrapper incorrectly; polyfills copied from Node/browsers relying on thenables goja cannot drive; field-name mismatches under the json tag mapper so 'then' resolves to undefined.

Related errors


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/29a1d97cd6800623. Report an issue: GitHub.