JavaScript Pause Release

From RuntimeWiki

Jump to: navigation, search

Contents

Title

JavaScript Coroutine Support (was JavaScript Pause Release)

Detailed write-up

There has been several contributions to this topic and below these are listed under separate headings.

Use-case 1: Pumping events to make the browser window responsive during long scripts (Andrew van der Meer)

Description

I think the most important basic feature is missing on the current list, the 'Pause'. It should do 2 things, pause for x milliseconds, and give control back to the system, so that it can empty the events/message queue, comparable with DoEvents from Visual Basic.

Why Is This Important?

It's needed because Javascript is single threaded, and such a feature would make applications with heavy calculations more responsive.

Possible Solution

A new API within JavaScript such as pause() which surrenders control to the platform so that (single-threaded) JavaScript logic doesn't take over the machine.

pause()
pause(milliseconds)

Use-case 2: Simplifying callback code using coroutines (Kris Zyp)

Description

Essentially we want to be able to write code that uses asynchronous actions and is intended to operate sequentially like this:

function sequence(urls) {
 for (var i = 0; i < 10; i++){
  var results = doAjaxRequest(urls[i]);
  animate();
 }
 function animate() {
  pause(100);
  updateDom();
 }
}

Rather than having to write it as we do currently:

function sequence(urls,callback) { // must modify the signature so callers will provide a callback
 var i = 0;
 function doRequestAndAnimate() {
  doAjaxRequest(urls[i],function(results) {
   animate(function() {
    i++;
    if (i < 10) {
     doRequestAndAnimate();
    }
   });
  });
  function animate(callback) {// must modify the signature so callers will provide a callback
   setTimeout(function() {
    updateDom();
    callback();
   },100);
  }
 }
 doRequestAndAnimate();
}

The second is obviously much more difficult to read, and error prone. Not only that but if functions are written with callbacks in case they need to carry out an asynchronous action, but they don't do anything asynchronous, this can lead to unbounded growth of the call stack and consequently recursion exceptions.

Why Is This Important?

Coroutines allow JavaScript code to be much simpler and concise. Coroutine support allows JavaScript to suspend and resume actions without requiring the callbacks that break encapsulation and can quickly create an ever expanding spaghetti of callbacks to callbacks. This is extremely valuable for writing concise simple functions to carry XHR requests, animations, and flow-based interaction.

Possible Solution

A new API that gives more complete, and non-polling dependent control over suspending and resuming execution: Constructor:

Future

methods

suspend(); // allows you to suspend execution until fulfill is called
fulfill(result); // fulfill can be called to resume execution

The pause function provides a subset of capability and can easily be written with the "Future" API:

function pause(millisecs) {
  var future = new Future();
  setTimeout(function() {
    future.fulfill()
  },millisecs);
  future.suspend();
}

Use-case 3: True-modal inline HTML dialogs (Mike Wilson)

Description

Many Ajax libraries provide dialog classes that let applications show modal dialogs that are actually implemented as positioned DIVs with some backdrop layer (possibly with dim transparency) hindering access to the rest of the page. Currently these have to be managed in "callback style" and can not be "programmed modally" like the built-in alert() and prompt() that let the calling event handler wait until the dialog is dismissed.

Also, these HTML dialogs can not be used for prompts/choices at page unload as they do not have the blocking behaviour of alert() and prompt().

This could be mitigated within the scope of coroutines.

Why is this important?

Avoiding callback-style code for scenarios that are essentially sequential is desirable for dialogs for the same reasons as described in use case 2. Having the support for letting such a dialog block at page unload is important because the page author may then implement all his dialogs with the same style in HTML, without having to resort to text-only "onbeforeunload" dialogs at page unload.

Possible solution

Avoiding callback-style coding is solved by the Future object in use-case 2. For the page unload scenario we could let Future.suspend() actually delay the page unloading until fulfill() has been called. This behaviour could either be by default (probably not good) or when a flag has been set in the suspend() call:

suspend( /*block unload*/ true )

To avoid problems with ill-behaved pages that block unload for bad reasons, the user should be able to break such modal states f ex using the Stop or navigation buttons. This topic is discussed further in Browser Unresponsive Mode Enhancements.

Enhanced dialogs are also discussed in Enhanced support for dialogs.

Technical discussion

This behavior is formally called coroutines, or as Microsoft calls the behavior provided by DoEvents, "cooperative threading". This behavior is often achieved using continuations in many languages (essentially the same effect). This is an enormously powerful capability, and can be used to do XHR requests, animations, and flow-based interaction without having to use complicated amounts of callbacks (CPS style coding). One of the great difficulties of JavaScript is that it is impossible to compose functions that may perform asynchronous actions. That is, if you call a function and that function may do something asynchronously, you must provide a callback for resuming after the action is completed, rather than just waiting for the function to finish. This is a terrible breach in encapsulation. Coroutines allow you to encapsulate asynchronous actions, and preserve encapsulation. Coroutines as a core functionality of JavaScript was rejected for the EcmaScript 4 specification. However, coroutine support certainly would be valuable as host/browser provided function. The pause function is one way of exposing this functionality. Pause has an advantage in that it is very simple, but consequently it can be limiting in how you can use it.

A very important related topic is that Firefox 3 has actually created an internal coroutine mechanism for synchronous XMLHttpRequests, that behaves almost exactly like the pause function, except that resuming the call stack is triggered by a response rather than a passing of duration of time. This new functionality solves the problem with browsers locking up on synchronous XMLHttpRequests. Certainly the hard part of coroutines is handling the suspension and resuming of the call stack, and this is already handled internally by Firefox 3. I have filed an enhancement request with Mozilla to expose this coroutine functionality in an API (https://bugzilla.mozilla.org/show_bug.cgi?id=425988). The API I suggested looks like: constructor: mozilla.Future methods: fulfill(result) suspend()

Therefore, to emulate FF3's synchronous XHR behavior with async XHR:

var xhr = new XMLHttpRequest
xhr.open("GET","resource",true);
var future = new mozilla.Future;
xhr.onload = function() {
 future.fulfill(); // fulfill the future and resume execution when onload is
called
}
xhr.send(...);
future.suspend();

Alternately, using the pause API to emulate FF3's synchronous XHR behavior:

var xhr = new XMLHttpRequest
xhr.open("GET","resource",true);
var done = false;
xhr.onload = function() {
 done = true;
}
while (!done) {
  pause(10);
}

The pause function is clunky, requiring a polling loop, rather than being able to resume execution immediately after the response is received.

Discussion

In this section, the contributors should express their opinions about this feature request, such as providing particular technical analysis or describing in prose why this feature is important (or not). It is recommended that each contributor create his own level-3 sub-section (e.g., === Jon Ferraiolo Comments ===).

Coach Wei Comments

Kris, this is a great addition. I completely agree that it is big missing feature in Javascript!

However, I'd like to see "pause" work in a way similar to Java's "wait" method (not sure whether this is what you mean or not): it will "suspend" the execution of the current Javascript code, but still enables the system (browser) to respond to other events, which may actually invoke some other javascript code. Upon the finish of the other Javascript code and the "time out" of the pause, the pause Javascript code will resume execution.

Kris Zyp Comments

Yes, we are on the same page as far as the meaning of "pause". The pause lets other events take place, just like FF3's synchronous XHR behavior. Of course there is a difference between a "pause" in a single threaded environment and Java's "wait" because, in Java other threads can execute regardless of whether "wait" is called. In Java, this is simply multi-threaded behavior. In JavaScript this is cooperative-threading (psuedo threads suspending to let other events take place), which is expessively equivalent to coroutines.

Andrew van der Meer Comments

Thanks for extending my original text. While it's true a Pause Release can make code much simpler, not mentioned yet are modal forms (like user drawn input and messageboxes) and screen updates while step by step debugging, I believe the only argument that will hold up in a discussion about it's importance, is the responsiveness in long loops. XHR requests and animations can be solved fairly easy with work arounds. For example the Sequence routine:

var sequence = function(urls, index) {
  index = ++index || 0;
  if (index > 0) { updateDom(); }
  if (index < 10) {
    var results = doAjaxRequest(urls[i]); 
    setTimeout(function() {sequence(urls, index)}, 100)
  }
}

The same routine with a pause would be more straightforward, less errorprone and easier to debug, but probably not convincing enough to persuade the inventors of Javascript to change the language over it. And that while a single threaded language without means to update the screen or handle UI events is severly crippled. Without it things like background compilers are impossible. So I personally would like to change the order of importance.


Possible risks of a Pause Release:

1) Unending event loop. The following could cause a stack overflow:

function onMouseMove() {
  pause();
}

2) Distributed computing malware stealing CPU cycles.

Mike Wilson's comments

The Future approach described above is almost exactly what I had in mind when Kris Zyp and I discussed this stuff on Brendan Eich's blog last year (see http://weblogs.mozillazine.org/roadmap/archives/2007/02/threads_suck.html).

I think it is a good pattern and it's also good that it has similarity with Java's wait/notify as many programmers are acquainted with this. Something missing from Future is the ability to specify a timeout, as pause(msec) allows. This could of course easily be implemented with a Future + setTimeout() as Kris shows, but one could also consider a suspend(msec) that returns null or exception if fulfill() is not called within the supplied time.

I think the use-case for inline HTML dialogs is an interesting one, especially modality behaviour at page unload, so I have made an attempt to add this to the text. I have also restructured this page to better show the different suggestions made. I hope that's ok with the other authors and please shout (or edit) if I've made some misguided edits.

Aliaksandr Valialkin's comments

The Future approach looks quite good, but it looks more natural when calling it Event instead.

// the Event object can be in two states - 'set' or 'reset'.
// It provides corresponding methods for changing it's state: set() and reset().
// By default newly created Event has 'reset' state.
// The Event provides also the wait() and waitWithTimeout() methods,
// which block callers until the event's state become 'set'
// or the given timeout exceeds.
// Note that multiple callers can block on the wait*() methods.
// The waitWithTimeout() method should return true if the set() method has been called
// for the event. Otherwise it should return false.
// Below are some use cases for the Event.

// a wrapper around async doSomethingAsync().
function doSomething() {
  var e = new Event();
  var completionCallback = function() {
    e.set();
  };
  doSomethingAsync(completionCallback);
  e.wait();
}

// yet another sleep() implementation
function sleep(timeout) {
  var e = new Event();
  e.waitWithTimeout(timeout);
}

// a wrapper around doSomethingAsync() with timeout
function doSomethingWithTimeout(timeout) {
  var e = new Event();
  var completionCallback = function() {
    e.set();
  }
  doSomethingAsync(completionCallback);
  var is_success = e.waitWithTimeout(timeout);
  if (!is_success) {
    discardDoingSomething();
  }
  return is_success;
}

Phase I Voting - Vote for Your Top 5 Features

NOTE: PHASE I VOTING IS NOW OPEN. (2008-04-01) We have now changed the voting procedure. Instead of putting votes on each separate wiki page, we are asking people to cast their Phase I Votes on the following wiki page:


Phase II Voting

More about this later.

Personal tools