scala-js/scala-js-dom

Promise.andThen signature with disjunction types

bblfish opened this issue · 12 comments

Currently andThen is defined

  def andThen[B](onFulfilled: js.Function1[A,B]): Promise[Any] = js.native

This loss of type in the response (it's of type Promise[Any] rather than Promise[B] makes it awkward when writing functions using the Fetch api where one would like to write things like

 def fetchListener(e: FetchEvent) = e.respondWith {
       fetch(e.request.url).andThen{
             response=> f(response),
             err => err 
      }
  }

where respondWith takes a Promise[Response]. With the current signature one needs to add an extra cast.

Instead the following signature seems more correct and closer to the JS documentation I have read

def andThen[B](onFulfilled: js.Function1[A,B]|js.Function1[A,Promise[B]]): Promise[B] = js.native

Here we use the disjunction type since andThen can either take a function to a value or a function to something Promisy.

Hmm. I've thought a little more about it and wouldn't a more correct typing would be:

def andThen[B](onFulfilled: js.Function1[A,B|Promise[C]]): Promise[B|C] = js.native

Since you could have a promise that conditionally returns either a value or a promise?

This would probably stack up quite quickly over several promises into some fairly nasty signatures :(

yes, you reveal an interesting possibility opened by the andThen function, namely that it can pass a function which on some input would return immediately and on others returns a promise that may be running on some other worker thread perhaps...

Note you'd either:

  1. need to put the C in the andThen method so that it is written andThen[B,C]...
  2. or you could have one type variable like this:
    def andThen[B](onFulfilled: js.Function1[A,B|Promise[B]]): Promise[B] = js.native
    and the programmer could use an either type when needed. Eg if a user is writing code that actually had a function that returns a Promise[C] or a D she could use the the function like this
fetch(url).andThen[CachedResponse|RemoteResponse]{ 
  case Response(401,headers) if headers.contains(`WWW-Authenticate`) => fetch(signed(url))
  case r@Response(200,_) => r 
}

Assuming that CachedResponse and RemoteResponse don't for some reason have a Response trait in common.

reason for current API returns [Any] : #101 (comment)

Ah ok, I propose to add that information about potentially infinitely nested Promises to the method documentation, as that is something that most developers would rarely end up working out for themselves. ( at least not run of the mill scala devs... ). Otherwise the danger is that someone transforms this andThen to a scala.Future without realising that this type of squashing may be appearing in the background...

Aside: Are there type systems that could deal with that?

Promises will likely be provided by the Scala.js core library in the next release (and include slightly better typing than currently provided, with caveats). You can track the progress here.

@mseddon Now they are. Should we directly migrate scala-js-dom to scalajs-0.6.7 or should we keep the scala-js-dom Promise until a later "major" release?

sjrd commented

I think we should immediately migrate to js.Promise, then immediately publish 0.9.0.

@danielwegener I strongly agree with @sjrd here, we want to encourage people to move onto the core library as soon as possible without confusion.

Cool, would be willing to do that, need a short break from slide preparations :)

:) nice one. Thanks!

There you go: #211

sjrd commented

Fixed in #211.