Movatterモバイル変換


[0]ホーム

URL:


Version 3.18.1

Promise

Jump to Table of Contents

Promises are a tool to help write asynchronous code in a more readable style that looks more like synchronous code.

In short, promises allow you to interact with a value that may or may not be available yet.

Promises let wrap, and even chain, asynchronous operations using a consistent API, avoiding writing nested anonymous callbacks (the "pyramid of doom"). And they let you handle any errors that happen during those operations.

TheY.Promise class is compatible with thePromises/A+ specification.

Getting Started

To include the source files for Promise and its dependencies, first loadthe YUI seed file if you haven't already loaded it.

<script src="http://yui.yahooapis.com/3.18.1/build/yui/yui-min.js"></script>

Next, create a new YUI instance for your application and populate it with themodules you need by specifying them as arguments to theYUI().use() method.YUI will automatically load any dependencies required by the modules youspecify.

<script>// Create a new YUI instance and populate it with the required modules.YUI().use('promise', function (Y) {    // Promise is available and ready for use. Add implementation    // code here.});</script>

For more information on creating YUI instances and on theuse() method, see thedocumentation for theYUI Global Object.

The Basics

As mentioned above, promises allow you to interact with a value that may or may not be available yet. In synchronous code, values are assigned to variables and immediately available to use, but if you need to use or assign a value that depends on an asynchronous operation to get, the rest of your code needs to be wrapped in a callback that is executed when that asynchronous operations completes.

Callbacks work, but they don't maintain any state, the APIs responsible for the callbacks are likely to differ, and they might not handle errors. It's also quite easy to find yourself building up multi-step transactions by nesting anonymous callbacks multiple levels deep.

Promises address this by providing an object that can be referred to immediately and any time in the future that represents the value produced by the asynchronous operation. Here's how you use them:

Two Simple Methods

Promises operate using two methods: theY.Promise constructor, and the promise instance'sthen() method.

// Create a promise for a valuevar promise = new Y.Promise(function (resolve, reject) {    var promisedValue;    var reasonForFailure = new Error('something bad happened');    // ...do some work to assign promisedValue, most likely asynchronously    // When the work is done, fulfill the promise with the resolve function,    // which was passed in the arguments.    resolve(promisedValue);    // Or if something went wrong, reject the promise with the reject function,    // also passed in the arguments.    reject(reasonForFailure);});// Do something with the promised value using the then() method. then() takes// two functions as arguments. promise.then(onFulfilled, onRejected);promise.then(    // aka onFulfilled    function (promisedValue) {        alert("Here's that value I promised I'd get for you: " + promisedValue);    },    // aka onRejected    function (reason) {        alert("Oh no! I broke my promise. Here's why: " + reason);    });

Creating a Promise

TheY.Promise constructor takes as its argument a function we'll call the "executor function". This function is responsible for saying when the promised value is ready, or notifying that something went wrong.

The executor function receives two customized functions as its arguments, commonly calledresolve andreject. If the work in the executor function to get the promised value completes successfully, pass the value to theresolve() method. If something went wrong, pass the reason—commonly anError—to thereject() method.

// dataPromise represents the data parsed from the IO response,// or the error that occurred fetching itvar dataPromise = new Y.Promise(function (resolve, reject) {    Y.io('getdata.php', {        on: {            success: function (id, response) {                // The IO completed, so the promise can be resolved                try {                    resolve(Y.JSON.parse(response.responseText));                } catch (e) {                    // any failure to produce the value is a rejection                    reject(e);                }            },            failure: function (id, response) {                // The IO failed                reject(new Error("getdata.php request failed: " + response));            }        }    });});

Resolving a Promise

Promises can be in one of three states:

  1. pending - the promised value is not ready yet (default)
  2. fulfilled - the value is ready
  3. rejected - something went wrong, the value can't be produced

"Resolving" a promise moves apending promise to eitherfulfilled orrejected, though the term is often used interchangeably with "fulfill" (it's good to have a positive outlook). Once a promise is fulfilled or rejected, it can't be transitioned to another state.

There are two ways promises get resolved. The first is using theresolve() function passed to the executor function in theY.Promise constructor. We'll talk about the second way when we discusspromise chaining.

Getting the Promised Value

Since the promised value probably isn't ready when you create the promise, you can't synchronously consume the value. Schedule the code that will use the promised value to execute with the promise'sthen() method.

then() takes two callbacks as arguments, that we callonFulfilled andonRejected. As you might have guessed, theonFulfilled callback is executed if the promise resolves to a value, and theonRejected callback is executed if it is rejected.

Only one of the callbacks will be executed, and only once.Both callbacks are optional, though in practice you'll always pass at least one tothen().

var stuff;var promise = new Y.Promise(getStuff);// When getStuff says the promise is fulfilled, update the stuff variable.// No onRejected callback is passed, so if there was an error, do nothing.promise.then(function (stuffValue) {    stuff = stuffValue;});// Stuff isn't populated yet because the promise hasn't been fulfilledconsole.log("Stuff value is " + stuff); // => "Stuff value is undefined"

You can callthen() on the promise as many times as you like. The same value will be passed to eachthen() callback.

Always Asynchronous

It's important to note that even if thegetStuff function above resolved the promise immediately, callbacks scheduled withthen willalways be called asynchronously. So the example code above will always log "Stuff value is undefined", regardless of whethergetStuff operates synchronously or asynchronously.

To limit the runtime impact ofthen callbacks always being executed asynchronously, they are scheduled usingY.soon(), which will attempt to avoid any minimum delay that some browsers impose onsetTimeout.

The Not-so Basics

Promise Chaining

Here's where things start getting fun. When you callpromise.then(...), a new promise is returned. The new promise will resolve when either of the original promise'sonFulfilled oronRejected functions returns a value or throws an error. This allows you to schedule several operations using chainedthen() calls.

// Verbose formstartSpinner();// Create the initial promisevar userDataLoaded = new Y.Promise(function (resolve, reject) {    Y.io('getUserData.php', {        data: 'id=1234',        on: {            success: function (id, response) {                try {                    resolve(Y.JSON.parse(response.responseText));                } catch (e) {                    reject(e);                }            },            failure: function (id, response) {                reject(new Error(response));            }        }    });});// after the user data is loaded, render stuff or show the loading errorvar uiUpdated = userDataLoaded.then(renderTemplates, showError);// after the UI is updated, stop the spinneruiUpdated.then(stopSpinner);// Concise form (more common)// Note Y.Promise can be called without 'new'Y.Promise(function (resolve, reject) { Y.io(...); })    .then(renderTemplates, showError) // returns another promise    .then(stopSpinner);               // returns another promise

A chained promise is resolved by the return value of the previous promise's callbacks. Or, if an error is thrown, the chained promise is rejected.

Note that functions will returnundefined if no explicitreturn statement is included. That will result in the promise being fulfilled with a value ofundefined. Sometimes that's okay, but it's often helpful to pass alongsome data.

function renderTemplates(userData) {    // Update the UI    Y.one('#userForm').setHTML(Y.Lang.sub(MyApp.userFormTemplate, userData));    // return a value to resolve the chained promise (aka uiUpdated) and pass    // to the uiUpdated's then(onFulfilled) callback, stopSpinner    return true;}function stopSpinner(updated) {    // updated will receive the return value of the previous promise's callback    // In this case, the boolean true.    var face = updated ? happyFace : sadFace;    spinnerNode.replace(face).hide(true);}// Using the original promise from the example aboveuserDataLoaded    .then(renderTemplates, showError)    .then(stopSpinner);

Handling Errors

When a promise is rejected, theonRejected callback (the second argument tothen()) is executed. LikeonFulfilled, it is called with whatever is passed to the executor function'sreject() function.

TheonRejected callback can then re-throw the error to propagate the failed state, or recover from the failure by returning a value. Again, without an explicitthrow orreturn, the callback will returnundefinedwhich will mark the failure as recovered, but with a resolved value ofundefined. This may not be what you want!

function showError(reason) {    Y.one('#userForm').hide(true);    Y.one('#message .details').setHTML(reason.message || reason);    Y.one('#message').show();    // Choosing not to re-throw the error, but consider it recovered from for    // the sake of this transaction. Returning false as resolved value to send    // to stopSpinner.    return false;}userDataLoaded    .then(renderTemplates, showError)    .then(stopSpinner);

BecauseshowError returned a value, and didn't re-throw thereason, the promise wrappingrenderTemplates andshowError was resolved to a "fulfilled" state with a value offalse. Since the promise was fulfilled, not rejected, that promise'sonFulfilled callback (stopSpinner) is called with the valuefalse.

Caveat: The Unhandled Rejection

Because thrown errors are caught by theY.Promise internals and used as a signal to reject a promise, it's possible to write promise chains that fail silently. This can be hard to debug.

To avoid this, it's highly recommended toalways include anonRejected callback at the end of your promise chains. The reason you only need to put one at the end is discussed below.

OmittingonFulfilled oronRejected

BothonFulfilled andonRejected callbacks are optional, though in practice, you will always pass at least one. When a callback isn't provided for athen() call in a promise chain, that promise is automatically fulfilled with the value returned from the prioronFulfilled callback or rejected with the reason thrown from the prioronRejected callback.

getHandleFromServerA()    .then(null, getHandleFromServerB)    .then(getUserData)    .then(renderTemplates, showError);// Same code, commented// Try to get a DB handle from Server A...getHandleFromServerA()    // if that fails, try Server B, otherwise, pass through the Server A handle    .then(null, getHandleFromServerB)    // if either server provided a handle, get user data.    // otherwise, there was an error, so pass it along the chain    .then(getUserData)    // render the user data if everything worked.    // if there was an error getting a DB handle or getting user data show it    .then(renderTemplates, showError);

It's not uncommon to see promise chains with onlyonFulfilled callbacks, then anonRejected callback at the very end.

Chaining Asynchronous Operations

As mentioned above, the return value from eitheronFulfilled oronRejected fulfills the promise with that value.There is one exception.

If you return a promise instead of a regular value (call itreturnedPromise), the original promise will wait forreturnedPromise to resolve, and adopt its state when it does. So ifreturnedPromise is fulfilled, the original promise is fulfilled with the same value, and ifreturnedPromise is rejected, the original promise is rejected with the same reason.

Y.Promise(function (resolve, reject) {        Y.io('getDataUrl.php', {            on: {                success: function (id, response) {                    resolve(response.responseText);                },                failure: function () {                    reject(new Error("Can't reach the server"));                }            }        });    })    // Chain another async operation by returning a promise.    // Don't worry, we'll wait for you.    .then(function (data) {        return new Y.Promise(function (resolve, reject) {            // Do another async operation            Y.jsonp(data.url, {                on: {                    success: resolve,                    failure: reject                }            });        });    })    // Called after both async operations have completed. The data response    // from the JSONP call is passed to renderTemplates    .then(renderTemplates)    // Then wait for 2 seconds before continuing the chain    .then(function () {        return new Y.Promise(function (resolve) {            setTimeout(resolve, 2000);        });    })    .then(hideMessage, showError);

Similarly, you can pass a promise to theresolve() function passed to theY.Promise executor function.

Caution: Do not pass a promise toreject() orthrow a promise from a callback. You're definitely doing something wrong if you find yourself doing that.

Y.when() For Promise Wrapping

If you're unsure if a variable has a value or a promise, or you want an API to support both value or promise inputs, useY.when(value) to wrap non-promise values in promises. Wrapped non-promise values will be immediately fulfilled with the wrapped value. Passing a promise toY.when will return the promise.

// Accept either a regular object or a promise to saveMyDatabase.prototype.save = function (key, data) {    // Ensure we are dealing with a promise and call then() to get its value    // return the promise chained off this then() call    return Y.when(data).then(function (data) {        // Store the data somehow, for instance in localStorage        localStorage.set(key, data);    });};

Non-serial Operation Batching

Promise chaining works great to serialize synchronous and asynchronous operations, but often several asynchronous operations can be performed simultaneously. This is whereY.Promise.all() comes in.

Y.Promise.all() takes an array of promises and returns a new promise that will resolve when all the promises in the array have resolved. The resolved value will be an array of values from the individual promises, in the order they were passed toY.Promise.all().

If any one of the promises is rejected, the returned promise is immediately rejected with that reason, so failures can be dealt with sooner rather than later.

Y.Promise.all([        getUserAccountInfo(userId),        getUserPosts(userId, { page: 1, postsPerPage: 5 }),        getUserRank(userId)    ])    .then(function (data) {        var account = data[0],            posts   = data[1],            rank    = data[2];        ...    }, handleError);

Racing promises

Sometimes it is necessary to provide early feedback when doing several transactions at the same time. It may be to send visual feedback to the user in the browser or to chose the fastest service to respond in Node.js. Promises provide this capability inY.Promise.race().

Y.Promise.race() takes an array of promises and resolves to the first promise that is fulfilled or rejected. Contrary toY.Promise.all() which waits for all promises to be resolved and fulfills to an array of all the corresponding values,Y.Promise.race() fulfills to a single value, that of the first promise in the list to resolve.

Y.Promise.race([        querySlowRemoteService(query),        getDataFromFastLocalCache(dataId)    ])    .then(function (raceWinner) {        ...    });

FAQ

What's the difference betweenY.Promise and...

Events?

Events are used to create a relationship between two objects, and better represent an open communication channel. Promises represent single values, and chains encapsulate transactions.

It's not uncommon to have event subscribers launch a promise chain, or to have events fired from within operations inside a promise chain. They are complementary tools.

Y.AsyncQueue

Y.AsyncQueue is a tool for splitting up long synchronous operations into asynchronous chunks to avoid blocking UI updates unnecessarily. It doesn't (as yet) support asynchronous steps. It also supports conditional looping and various other things that promises don't, out of the box.

Y.Parallel

Y.Parallel is similar toY.Promise.all in that it provides a mechanism to execute a callback when several independent asynchronous operations have completed. However, it doesn't handle errors or guarantee asynchronous callback execution. It is also transactional, but the batch of operations is bound to a specific callback, whereY.Promise.all() returns a promise that represents the aggregated values of those operations. The promise can be used by multiple consumers if necessary.

What are the plans for Promises in the library?

There are a lot of opportunities inside YUI to move transactional APIs to consume and/or return promises rather than use callbacks or one-time events. While there are no set plans for which APIs will be changed or in what priority order, you can expect to see promises showing up across the library in the near future.

API Docs

Table of Contents

Examples


[8]ページ先頭

©2009-2025 Movatter.jp