Home > Net >  Ajax.post --> dom.fetch
Ajax.post --> dom.fetch

Time:12-20

I'm trying to use the dom.fetch (or dom.Fetch.fetch) api instead of Ajax.post and have a few problems:

Is this a correct translation from ajax to fetch?

Ajax.post(
  url = "http://localhost:8080/ajax/myMethod",
  data = byteBuffer2typedArray(Pickle.intoBytes(req.payload)),
  responseType = "arraybuffer",
  headers = Map("Content-Type" -> "application/octet-stream"),
)

dom.fetch(
  "http://localhost:8080/fetch/myMethod",
  new RequestInit {
    method = HttpMethod.POST
    body = byteBuffer2typedArray(Pickle.intoBytes(req.payload))
    headers = new Headers {
      js.Array(
        js.Array("Content-Type", "application/octet-stream")
      )
    }
  }
)

A "ReferenceError: fetch is not defined" is thrown on the js side though, same if replacing with dom.Fetch.fetch.

My setup:

Fresh jsdom 19.0.0 with

npm init private
npm install jsdom

project/plugins.sbt

libraryDependencies  = "org.scala-js" %% "scalajs-env-jsdom-nodejs" % "1.1.0"
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.8.0")

build.sbt (in js project)

libraryDependencies  = "org.scala-js" %%% "scalajs-dom" % "2.0.0"
jsEnv := new JSDOMNodeJSEnv(JSDOMNodeJSEnv.Config()
  .withArgs(List("--dns-result-order=ipv4first")))

Thought that the jsEnv workaround was not needed on Scala.js 1.8 (see https://github.com/scala-js/scala-js-js-envs/issues/12#issuecomment-958925883). But it is still needed when I run the ajax version. With the workaround, my ajax version works fine, so it seems that my node installation is fine.

CodePudding user response:

Got help on the scala-js channel on Discord from @Aly here and @armanbilge here who pointed out that:

  • fetch is not available by default in Node.js or JSDOM, only in browsers.
  • scala-js-dom provides typesafe access to browser APIs, not Node.js APIs.

The distinction between browser API and Node API wasn't clear for me before, although it is well described in step 6 of the scala-js tutorial.

So, dom.fetch of the scala-js-dom API works when running a js program in a browser, but not if running a test that uses the Node jsEnv(ironment)! To fetch in a test one would have to npm install node-fetch and use node-fetch, maybe by making a facade with scala-js.

Since I want my code to work for both browser (scala-js-dom) and test (Node.js), I ended up falling back to simply using the Ajax.post implementation with XMLHttpRequest:

case class PostException(xhr: dom.XMLHttpRequest) extends Exception {
  def isTimeout: Boolean = xhr.status == 0 && xhr.readyState == 4
}

val url         = s"http://$interface:$port/ajax/"   slothReq.path.mkString("/")
val byteBuffer  = Pickle.intoBytes(slothReq.payload)
val requestData = byteBuffer.typedArray().subarray(byteBuffer.position, byteBuffer.limit)
val req         = new dom.XMLHttpRequest()
val promise     = Promise[dom.XMLHttpRequest]()

req.onreadystatechange = { (e: dom.Event) =>
  if (req.readyState == 4) {
    if ((req.status >= 200 && req.status < 300) || req.status == 304)
      promise.success(req)
    else
      promise.failure(PostException(req))
  }
}
req.open("POST", url) // (I only need to POST)
req.responseType = "arraybuffer"
req.timeout = 0
req.withCredentials = false
req.setRequestHeader("Content-Type", "application/octet-stream")
req.send(requestData)

promise.future.recover {
  case PostException(xhr) =>
    val msg    = xhr.status match {
      case 0 => "Ajax call failed: server not responding."
      case n => s"Ajax call failed: XMLHttpRequest.status = $n."
    }
    println(msg)
    xhr
}.flatMap { req =>
  val raw       = req.response.asInstanceOf[ArrayBuffer]
  val dataBytes = TypedArrayBuffer.wrap(raw.slice(1))
  Future.successful(dataBytes)
}

CodePudding user response:

The fetch API is only available by-default in browser environments, and not in Node. node-fetch is also not pulled in (or at least not re-exported) by jsdom, so fetch is not available with the current package/environment setup.

Possible solutions:

  1. Set the ScalaJS side up in such a way that it would call node-fetch on NodeJS and fetch on browser
  2. Use XMLHttpRequest which is available on both platforms

(Please see here in the #scala-js channel in the Scala Discord for an explanation on why this answer is short. TL;DR, Marc requested I answer here so I can receive the rep. bounty for helping in the Discord. A screenshot of the context is available.)

  • Related