Skip to content

Server returns html document with [object Promise]Β #1

@Nikjee

Description

@Nikjee

So i was really curious with this go ssr approach and went to try it but there was several issues.

  1. I had problems running docker container, running on M1 MBP and this dockerfile worked for me
FROM node:18-alpine as frontend

RUN npm install -g [email protected]

WORKDIR /app
COPY client ./client/

WORKDIR /app/client
RUN pnpm install --frozen-lockfile && pnpm build


FROM --platform=linux/amd64 ubuntu:latest as backend

# RUN apt update

ARG GO_VERSION
ENV GO_VERSION=${GO_VERSION}

RUN dpkg --add-architecture amd64
RUN apt-get update && apt-get upgrade
RUN apt-get install -y wget git gcc libc-dev make build-essential g++-x86-64-linux-gnu libc6-dev-amd64-cross gcc-aarch64-linux-gnu binutils:amd64 libc6-amd64-cross libstdc++6-amd64-cross

RUN ln -s /usr/x86_64-linux-gnu/lib64/ /lib64

ENV LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/lib64:/usr/x86_64-linux-gnu/lib"
# RUN apt install gcc libc-dev make build-base

RUN wget -P /tmp "https://dl.google.com/go/go1.22.2.linux-amd64.tar.gz"

RUN tar -C /usr/local -xzf "/tmp/go1.22.2.linux-amd64.tar.gz"
RUN rm "/tmp/go1.22.2.linux-amd64.tar.gz"

ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"

WORKDIR /app

COPY --from=frontend /app/dist ./dist

COPY pkg/ ./pkg/
COPY go.mod go.sum Makefile main.go ./
RUN go mod download

RUN make build


FROM --platform=linux/amd64 ubuntu:latest as final

WORKDIR /app

COPY --from=backend /app/build/ /app/

EXPOSE 8080

CMD ["/app/server"]

And Makefile, not sure if it did anything but it is what it is

build:
	# build the backend
	env GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o $(BUILD_DIR)/$(APP_NAME) main.go

  1. As title says server returns a Promise in a string so for that i had to find a way to wait for it, on v8 repo in issues i found this posted
func resolvePromise(ctx *v8go.Context, val *v8go.Value, err error) (*v8go.Value, error) {
	if err != nil || !val.IsPromise() {
		return val, err
	}
	for {
		switch p, _ := val.AsPromise(); p.State() {
		case v8go.Fulfilled:
			return p.Result(), nil
		case v8go.Rejected:
			return nil, errors.New(p.Result().DetailString())
		case v8go.Pending:
			ctx.PerformMicrotaskCheckpoint() // run VM to make progress on the promise
			// go round the loop again...
		default:
			return nil, fmt.Errorf("illegal v8go.Promise state %d", p) // unreachable
		}
	}
}

And in a nutshell what we need to do is this

func (r *Renderer) Render(urlPath string) (string, error) {
	iso := r.pool.Get()
	defer r.pool.Put(iso)

	ctx := v8go.NewContext(iso.Isolate)
	defer ctx.Close()

	iso.RenderScript.Run(ctx)

	renderCmd := fmt.Sprintf(`ssrRender("%s")`, urlPath)
	val, err := ctx.RunScript(renderCmd, r.ssrScriptName)
	result, _ := resolvePromise(ctx, val, err)

	if err != nil {
		if jsErr, ok := err.(*v8go.JSError); ok {
			err = fmt.Errorf("%v", jsErr.StackTrace)
		}
		return "", nil
	}

	return result.String(), nil
}

Maybe for the future there should be a check if a value of a script is a Promise then we wait for it otherwise just return a string.

I'm not sure if you want to add this edits but for people who will come upon this might help

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions