timer extension (promo)
Book notes:
“I am surprised and pleased at the number of ways Promises can be used to effectively manage asynchronous code.”
“Supplemental material (code examples, exercises, etc.) is available for download at https://github.com/dxparker/promises-book-examples.”
“In short, a callback is a function provided to other code for invocation.”
“As long as you have a reference to a function, you can use it as a callback.”
“window. requestAnimationFrame(). Its callback is invoked between browser repaint intervals”
“Synchronous code can be easier to understand because it executes in the order it is written.”
“In JavaScript, callbacks are used to manage the execution order of any code that depends on an async task.”
“Remember that a promise serves as a placeholder for the result of an operation.”
“When a promise is no longer pending it is said to be settled. This is a general term indicating the promise has reached its final state. Once a pending promise is settled the transition is permanent. Both the state and any value given as the result cannot be changed from that point on. This behavior is consistent with how operations work in real life. A completed operation cannot become incomplete and its result does not change.”
“promise is a placeholder for the result of one attempt of an operation”
“The immutability of a settled promise makes code easier to reason about. Allowing the state or value to change after a promise is fulfilled or rejected would introduce race conditions. Fortunately, the state transition rules for promises prevent that problem.”
“Since the reject function transitions a promise to the rejected state, why does the resolve function transition a promise to a state called fulfilled instead of resolved? Resolving a promise is not the same as fulfilling it. When the argument passed to resolve is a value, the promise is immediately fulfilled. However, when another promise is passed to resolve, such as promise. resolve (otherPromise), the promises are bound together. If the promise passed to resolve is fulfilled, then both promises will be fulfilled. And if the promise passed to resolve is rejected, then both promises will be rejected. In short, the argument passed to resolve dictates the fate of the promise. Figure 2-2 shows this process.”

“The resolve and reject functions can be called without an argument, in which case the fulfillment value or rejection reason will be the JavaScript type undefined. The Promise API also provides two convenience methods (see Example 2-7) for creating a promise that is immediately resolved or rejected: Promise. resolve() and Promise. reject().”
“cases. The resolver function passed to the Promise constructor executes synchronously. And all callbacks passed to then and catch are invoked asynchronously.”
“Rejections and errors propagate through promise chains. When one promise is rejected all subsequent promises in the chain are rejected in a domino effect until an onRejected handler is found. In practice, one catch function is used at the end of a chain (see Example 2-12) to handle all rejections. This approach treats the chain as a single unit that the fulfilled or rejected final promise represents.”
“Promises are also rejected when an error is thrown in a callback passed to then or in the resolver function passed to the Promise constructor.”
“any value, including undefined, can reject promises, we recommend using an error object. Creating an error can capture the call stack for troubleshooting and makes it easier to treat the argument the catch handler receives in a uniform way… Using JavaScript Error objects to reject promises can capture the call stack for troubleshooting and makes it easier to treat the argument the catch handler receives in a uniform way.”
“new Promise(function (resolve, reject) {... }) returns promise”
“The Promise global is a constructor function that the new keyword invokes. The Promise global creates promise objects that have the two methods then and catch for registering callbacks that are invoked once the promise is fulfilled or rejected”
“promise. then([onFulfilled], [onRejected]) returns promise”
“The promise.then() method accepts an onFulfilled callback and an onRejected callback. People generally register onRejected callbacks using promise.catch() instead of passing a second argument to then”
“promise.catch(onRejected) returns promise”
“The promise.catch() method accepts an onRejected callback and returns a promise that the return value of the callback or any error thrown by the callback resolves or rejects, respectively. That means any rejection the callback given to catch handles is not propagated further unless you explicitly use throw inside the callback.”
“Promise.resolve([value|promise]) returns promise”
“The Promise.resolve() function is a convenience function for creating a promise that is already resolved with a given value. If you pass a promise as the argument to Promise.resolve(), the new promise is bound to the promise you provided and it will be fulfilled or rejected accordingly.”
“Promise.reject([reason]) returns promise”
“The Promise.reject() function is a convenience function for creating a rejected promise with a given reason.”
“Promise.all(iterable) returns promise”
“The Promise.all() function maps a series of promises to their fulfillment values. It accepts an iterable object such as an Array, a Set, or a custom iterable. The function returns a new promise fulfilled by an array containing the values in the iterable. Corresponding fulfillment values in the resulting array replace any promises contained in the iterable. The new promise that the function returns is only fulfilled after all the promises in the iterable are fulfilled, or it is rejected as soon as any of the promises in the iterable are rejected. If the new promise is rejected it contains the rejection reason from the promise in the iterable that triggered the rejection. If you are working with a Promise implementation that does not understand ES iterables, it will likely expect standard arrays instead”
“Promise.race(iterable) returns promise”
“The Promise.race() function reduces a series of items to the first available value. It accepts an iterable and returns a new promise. The function examines each item in the iterable until it finds either an item that is not a promise or a promise that has been settled. The returned promise is then fulfilled or rejected based on that item. If the iterable only contains unsettled promises, the returned promise is settled once one of the promises in the iterable is settled”
“Keep these three points in: 1) A promise is a placeholder for a value that is usually the result of an asynchronous operation. 2) A promise has three states: pending, fulfilled, and rejected. 3) After a promise is fulfilled or rejected, its state and value can never be changed.”
“As a general rule, any function that uses a promise should also return a promise. When a promise is not propagated, the calling code cannot know when the promise is fulfilled and thus cannot effectively perform work after the fact. It’s easy to ignore this rule when writing a function whose caller does not care when the async work is finished, but don’t be fooled. It’s much easier to return a promise that initially goes unused than to retrofit promises into a series of functions later on.”
// Example 3-3. Substituting a resolved promise
function showMainMenu() {
var p = (!user.authenticated) ? user.login() : Promise.resolve();
return p.then(function () {
// ... Code to display main menu
});
}
“Building long chains of promises may require significant amounts of memory. Be sure to instrument and test your code to guard against unexpected performance problems.”
“promises from different libraries can be used with one another. The basis of all interoperability between promise implementations is the thenable contract. Any object with a then(onFulfilled, onRejected) method can be wrapped by any standard promise implementation.”
“In Query, deferred objects represent async operations. A deferred object is like a promise whose resolve and reject functions are exposed as methods.”
“For detailed information on web workers, refer to Web Workers: Multithreaded Programs in JavaScript by Ido Green (O’Reilly.)”
“Any error that occurs in a function that returns a promise should be used to reject the promise instead of being thrown back to the caller. This approach allows the caller to deal with any problems that arise by attaching a catch handler to the returned promise instead of surrounding the call in a try/catch block.”
“Remember that a promise invokes each callback in a separate turn of the event loop.”
“Handling errors in asynchronous code cannot be done with traditional try/catch blocks. Fortunately, promises have a catch method for handling asynchronous errors.”
“Objects that want to expose a series of items can implement the iterable interface by defining a function whose name is the value of Symbol. iterator, that is, object[Symbol. iterator] = function () {...}. This function should return an object with the iterator interface. The iterator interface has one method named next. The method returns an object with two properties named value and done. The value represents the current item in the iteration and the done property is a flag to indicate when there are no more values available from the iterator.”
“Arrays are iterables so they contain the Symbol. iterator method, as shown in Example 6-10.”
// Example 6-10. Using the iterable interface of an array
var array = [1, 2];
var iterator = array[Symbol.iterator]();
iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: 2, done: false }
iterator.next(); // { value: undefined, done: true }
“How do iterables relate to promises? The Promise.all() and Promise.race() functions both accept iterables. Although an array is probably the most common type of iterable you would use with these functions, other options are available. For instance, the Set datatype in ESo is also an iterable. A set is a collection of items that does not contain duplicates. You can pass a set to Promise.all() or you can use a custom iterable by implementing the interface on an object you define.”
“ES6 includes a feature called generators that allows you to write async code that looks synchronous.”
“A generator is a special type of function that can pause its execution to pass values back to its caller and later resume executing where it left off. This ability is useful for generating a series of values.”
// Example 6-15. Computing a series of values using a generator
function* fib() {
var a = 0;
var b = 1;
while (true) {
yield a + b;
b = a + b;
a = b - a;
}
}
var i;
var result;
var iterator = fib();
for (i = 0; i < 5; i++) {
result = iterator.next();
console.log(result.value);
}
“The fib function is now a generator, which is indicated by adding the * at the end of the function keyword. When the generator is called, the JavaScript engine does not start running the code inside fib as it would with a normal function. Instead the call to fib returns an iterator. The iterator is used to pause and resume execution of the generator and pass values between the generator and the calling code. The code inside fib starts running the first time iterator.next() is called. Execution continues until the yield keyword. At that point the function pauses and sends the result of the yield expression back to the calling code as the return value of iterator next(). The result is an object that provides the outcome of the yield statement in a property named value. When iterator.next() is called again the code inside fib resumes execution on the line after the yield statement. The values of a and b are updated and the next iteration of the while loop hits the yield statement, which repeats the pause and send behavior for another number in the sequence. A generator may contain multiple yield statements but in this case it has one yield placed inside an infinite while loop. The loop allows the iterator to provide an indefinite amount of Fibonacci numbers. In the previous example the calling code stopped making requests after five values. Example 6-15 introduced three concepts: the generator declaration syntax, the iterator, and the yield keyword. That’s a lot to comprehend at once but all three are necessary to create a basic example. Consider reviewing the previous snippet and explanation until you are comfortable with these concepts.”
“Not only can values be passed from the generator back to the calling code, they can also be passed from the calling code into the generator. The iterator.next() method accepts a parameter that is used as a result of the yield expression inside the generator. Example 6-16 demonstrates passing a value into the generator. In this case a function counts things one at a time by default but can be adjusted to count in any increment.”
// Example 6-16. Passing values into the generator
function* counter() {
var count = 0;
var increment = 1;
while (true) {
count = count + increment;
increment = (yield count) || increment;
}
}
var iterator = counter();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
console.log(iterator.next(10).value); // 13 <- Start counting by 10
console.log(iterator.next().value); // 23
console.log(iterator.next().value); // 33
“The fourth call to iterator next() sets the increment value to 10. All the other calls to iterator.next() pass a value of undefined by not providing an explicit argument.”
“A generator can also declare parameters similar to a traditional function. The values for these parameters are set when the iterator is created and they may act as a configuration for the iterator. Example 6-17 is a revised version of the counter whose initial increment can be set by a parameter.”
// Example 6-17. Configuring an iterator with an initial parameter
function* counter(increment) {
var count = 0;
increment = increment || 1;
while (true) {
count = count + increment;
increment = (yield count) || increment;
}
}
var evens = counter(2);
console.log('Even numbers'); // Even numbers
console.log(evens.next().value); // 2
console.log(evens.next().value); // 4
console.log(evens.next().value); // 6
var fives = counter(5);
console.log('Count by fives'); // Count by fives
console.log(fives.next().value); // 5
console.log(fives.next().value); // 10
console.log(fives.next().value); // 15
“We’ve discussed how values can be passed to generators using the initial parameters and as an argument to iterator.next(). However, there are two cases where the argument to iterator.next() is ignored. The argument is always ignored the first time iterator.next() is called. Example 6-18 shows the value being ignored followed by an explanation of why it happens.”
// Example 6-18. The parameter in the first call to iterator.next) is always ignored
// Same function* counter as previous example
function* counter(increment) {
var count = 0;
increment = increment || 1;
while (true) {
count = count + increment;
increment = (yield count) || increment;
}
}
var iterator = counter(5); // <- Initial increment is 5
console.log(iterator.next(3).value); // 5 <- 3 is ignored
console.log(iterator.next().value); // 10
console.log(iterator.next(200).value);// 210 <- Increment by 200
console.log(iterator.next().value); // 410
“The other case where an argument to iterator.next() is ignored is after the function returns. All the previous examples contain infinite loops that paused the function to send back a value. When a generator function returns from execution in the traditional sense as opposed to pausing on yield, there is no way to receive more data from the iterator. Example 6-19 is a generator that returns after filtering objects in an array.”
// Example 6-19. Finite iterations
function* match(objects, propname, value) {
var i;
var obj;
for (i = 0; i < objects.length; i++) {
obj = objects[i];
if (obj[propname] === value) yield obj;
}
}
var animals = [
{ type: 'bird', legs: 2 },
{ type: 'cat', legs: 4 },
{ type: 'dog', legs: 4 },
{ type: 'spider', legs: 8 }
];
var iterator = match(animals, 'legs', 4);
console.log(iterator.next().value.type); // value is an animal
console.log(iterator.next().value.type); // value is an animal
console.log(iterator.next().value); // value is undefined
// Console output:
// cat
// dog
// undefined
“A new for...of construct, as shown in Example 6-21, allows you to implicitly manage the iterator. Use for...of if you are dealing with a finite number of iterations and do not need to pass values back to the generator.”
// Example 6-21. Using an implicit iterator created by for...of
// Better substitute for iterator and loop
for (animal of match(animals, 'legs', 4)) {
console.log(animal.type);
}
// Example 6-22. Throwing errors with the iterator
function* languages() {
try {
yield 'English';
yield 'French';
yield 'German';
yield 'Spanish';
} catch (error) {
console.log(error.message);
}
}
var greetings = {
English: 'Hello',
French: 'Bonjour',
Spanish: 'Hola'
};
var iterator = languages();
var result;
var word;
while ((result = iterator.next()).done !== true) {
word = greetings[result.value];
if (word) console.log(word);
else iterator.throw(Error('Missing translation for ' + result.value));
}
// Console output:
// Hello
// Bonjour
// Missing translation for German
No comments:
Post a Comment