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
- Return genuine promises from stream script code — async functions or the engine's own fetch/stream APIs
- Do not override Promise in scripts run by this engine
- If a custom thenable is required, give it a real function then(onFulfilled, onRejected)
- 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
- Use the engine's own fetch/stream APIs instead of reimplementing thenables
- Never monkey-patch Promise inside injected scripts
- Log returned values (typeof/keys) when developing resolver scripts
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.