Partager via


Windows Store apps using JavaScript versus traditional web apps

[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]

This topic provides information about the differences in coding styles used in existing Web apps written in JavaScript and Windows Store apps using JavaScript. It provides guidelines for Web developers to understand how code that is optimized for Windows relates to apps that are meant to be migrated among platforms with ease. It assumes that the reader is familiar with JavaScript programming and World Wide Web Consortium (W3C) standards.

Introduction

The Windows 8 provides a platform for building new Windows Store apps for Windows. Windows Store apps can be written in different programming languages including ECMAScript 5 compliant JavaScript.

The Windows Runtime enables apps that can leverage the power and broad capabilities of Windows combined with the strength, ubiquity and simplicity of standards-based web technologies.

In fact, when authoring an app in JavaScript, the developer may choose to use existing web standards. This allows the greatest interoperability with other standards-compliant platforms. However, the Windows Runtime also enables developers to build Windows Store apps with JavaScript that are optimized for the Windows platform.

Depending on your objectives, the best approach to architect your apps will differ. The choice becomes easier if you consider the guidance that follows.

Windows Runtime JavaScript design patterns

The Windows Runtime offers a development environment that allows programmers to leverage their existing web development experience. By and large, the Windows Runtime APIs will look and feel familiar to experienced Web developers. They represent a clean extension of standards based web development APIs, and are available for use when needed, without forcing developers to import foreign constructs in order to make existing code work. However, Windows Runtime APIs do empower the developer to exert much more granular control over some aspects of the platform (e.g. interfacing with I/O devices).

The Windows Runtime also introduces some coding patterns to the Windows platform that may be new to Web developers who are used to the patterns defined in certain W3C specifications, as well as some of the most popular frameworks built on top of them.

The next few sections will touch on these patterns and briefly explain their rationale.

Asynchronous programming on the web

By nature, Web apps are susceptible to much longer delays between requests and responses than apps that only access local resources. Synchronous (blocking) calls to a remote server would freeze the UI of a web app with unacceptable impact on the user experience. Multithreading could be used to work around the problem, the price however is a significant increase in architectural complexity. That is why JavaScript APIs typically implement asynchronous programming constructs, and most standard APIs leverage them extensively.

The Windows Runtime adopts asynchronous programming across the board, by providing a wealth of I/O capabilities to developers and applying this concept to new kinds of disk, network and device interactions. It does so by using proven paradigms already seen in several programming languages as well as in some popular JavaScript frameworks.

Developers can build apps by leveraging components as diverse as FileReader, Web Sockets, Geolocation, IndexedDB and others, always using the same basic template. The end result is code that is easier to read and maintain, especially when multiple asynchronous operations depend on each other. The Windows Runtime makes following the flow of cascading callbacks in the code -- traditionally a non-trivial exercise even for an experienced developer-- much easier and more intuitive.

Promises

In the Windows 8, asynchronous programming in JavaScript is handled via the Promises pattern, which is also used in other popular JavaScript toolkits. Promises provide a uniform way to program against asynchronous results, the ability to compose asynchronous operations, and a unified asynchronous error handling model.

What is a promise?

A promise is an object you can pass around that represents a value which may not yet have been computed, an error which has not yet been found, or some work which has not yet been performed.

  • A promise is a mechanism that simplifies the programming model for consuming asynchronous components.
  • A promise is a means to create a composable wrapper over your asynchronous operations.

How do I use promises?

The API for consuming promises is simple. In fact, it only contains two instance methods:

.then(onComplete, onError, onProgress)
.cancel()

The .then() method takes handler(s) as input which will be called at some point when the promised value is available, an error has occurred in computing the value, or progress has been made towards its availability. The value returned by then() is a promise for the return value from calling either the onComplete or onError handler. If an exception occurs while executing either the onComplete or onError handler the returned promise will be fulfilled with an error value which is the exception value.

Each of the handlers is passed a single value which is the state relevant to that handler.

  • In the onComplete handler, it is the promised value.
  • In the onError handler it is the error that occurred.
  • In the onProgress handler it is some value defined by the operation (basically something related to the promised value).

Any of the arguments may be undefined. If the onComplete argument is undefined it is as if the developer passed:

function (v) { return v; }

If the onError argument is undefined it is as if the developer passed:

function (e) { throw e; }

If the onProgress argument is undefined it is as if the developer passed:

function () { }

The .cancel() method can be used to request that a promise and all non-computed work on which it depends be discarded.

Promises make composable asynchrony easy. For instance, reading the text from a file in the Windows Runtime requires a number of asynchronous operations. With a promise-compatible language, the various APIs can be easily composed into a simple readAllTextAsync() function.

function readAllTextAsync(path) {
    return Windows.Storage.getFileItemFromPathAsync(path).
       then(function (item) { 
           var mode = Windows.Storage.FileAccesMode.read;
           return item.getStreamAsync(mode);
       }).
       then(function (seeker) {
           var bbrw 
         = new Windows.Storage.BasicBinaryReaderWriter();
           return bbrw.readStringAsync(seeker.getReaderAt(0));
       });
}

A consumer of readAllTextAsync() now only needs to worry about the path to the file and what to do with the result:

    readAllTextAsync(path).
        then(console.log);

This functionality would be way more cumbersome and error prone if repackaged in a more conventional coding style, where callbacks are used in lieu of the .then construct. The chaining of asynchronous calls in this paradigm would look something like the following, which (especially if onError and onProgress clauses were added) would be much harder to maintain:

doSomethingAsync(1, 2, function (result) {
    doSomethingAsync(result, 5, function (result) {
        doSomethingAsync(result, 10, function (result) {
            console.log(value);
        });
    });
});

DOM events

The W3C documentation defines DOM Events as "a generic platform and language-neutral event system which allows registration of event handlers, describes event flow through a tree structure, and provides basic contextual information for each event." The Windows Runtime implements a relevant subset of the DOM Level 3 Events Specification.

In particular, in Windows Store apps, system events are exposed in the same way as DOM events: therefore, a developer can invoke the addEventListener() and removeEventListener() functions and associated semantics to manage them. As a result, one can exert a very granular control over what happens in the UI and how the program should respond to it, without having to learn any new skills.

One notable area of differentiation with the DOM Events specification is that the Windows Runtime discourages Windows Store apps from raising events artificially (e.g. mouse and keyboard events). For this reason, the dispatchEvent() method is not supported. Security and code maintainability considerations were among the primary drivers of this choice.

Events are sometimes generated artificially by developers for UI Testing and Automation purposes. In most cases however doing so is unnecessary, provided that the right design patterns are adopted instead. Windows Store app developers are recommended to architect their apps by cleanly separating the view from the underlying data model (e.g. using the MVC pattern). That way, automation can directly target the business logic instead of impersonating the user at the GUI level.

Bubbling and capturing, and certain associated properties that would otherwise be passed to the event handler, are also not part of the Windows Runtime API dispatching workflow. This is a natural consequence of the fact that Windows Runtime events are not naturally associated to a tree structured DOM. Therefore, in general there is no object hierarchy across which such events need to be bubbled.

Namespaces

Windows Runtime is quite a large platform, comprising more than 800 individual classes and enums. Care had to be used during design to result in the most intuitive namespace structure possible, while also considering potential future growth.

W3C specifications typically add to the global namespace. This global namespace pollution can be convenient, but is already facing scalability issues as the number of APIs in the web platform increases at a rapid pace. Given the size of the Windows Runtime, and the independent evolution of the Windows and web platforms, it would be impractical to expose Windows APIs in the global environment.

The end result is a namespace hierarchy in which APIs were categorized into easily discoverable areas of functionality. This hierarchy is neither too flat (aligning too many objects at root level would be confusing and impractical) nor too deep (which can result in hard to read, non-intuitive code). In some cases, to avoid confusion due to naming similarity of classes that perform very different tasks, differences are intentional. In all cases there is clear disambiguation between Windows Runtime objects and standards based objects. This careful balancing act yielded a naming structure that differs somewhat from what one can find in browser programming samples available on the web.

For example, to access Geolocation functionality described in the W3C Geolocation specification, one must call:

navigation.geolocation

Whereas Windows Runtime exposes this functionality thusly:

Windows.Devices.Geolocation.Geolocator()

The first and most obvious difference is the use of the "Windows" root namespace, which is the only global object exposed by the Windows Runtime. One major purpose of putting classes under it is to clearly distinguish anything that leverages the unique capabilities of Windows. That also allows functionality based on web standards to coexist for simplified portability.

In those cases where spelling out multi-level namespaces in full is unnecessary, it is convenient to be able to alias the namespace at the beginning of the program in order to keep code verbosity to a minimum, like this:

var geo = Windows.Devices.Geolocation;
///...
var x = new geo.Geolocator();

In addition, the use of namespaces helps make APIs more easily discoverable in development tools and documentation.

Enumerations

Objects defined in web standards set properties from a small set of acceptable values using a variety of methodologies. They do use enumerations on occasion, but they often accept strings. An important reason for this is to leave room for experimentation by different organizations, before new properties become standardized (which is easier if simple strings are used to assign and designate them). Partly because it has no such need, the Windows Runtime has a much stronger bias towards enumerations.

This choice trades the simplicity of a statement like:

reader.readAsText(readFile, "UTF-16");

In favor of the Windows Runtime syntax:

reader.unicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.utf8;
reader.readString(someValue);

This syntax is more robust, and less likely to contain misspellings or other issues. Legal values of parameters are also far easier to discover. Furthermore, enumerations allow for consistency and interoperability across the programming languages supported by the Windows platform.

Constructors

Although W3C specs use a number of different styles to create objects, which include the use of constructors, many describe singleton JavaScript objects that are implicitly instantiated, and perform their duties through a multitude of methods and properties. An example is the navigator object mentioned in previous sections of this topic.

In the Windows Runtime, similar APIs may be exposed differently. While consistency with existing web standards was sought whenever possible, in the end the most appropriate design for each feature was adopted. In particular, the Windows Runtime is subdivided in a more granular set of related but specialized classes, each of which generally gets instantiated through its own constructor or through a static factory method.

Visual Studio simplifies considerably the generation of such statements thanks to the integration of Intellisense (code completion). Furthermore, syntactical mistakes are identified on the fly, thereby helping developers create code that builds and runs more reliably.

HTML5 DOM Level 3 Events Specification

Common JS Promises/A proposed standard Wiki