Adam Terlson
Software Engineer
@adamterlson
adam.terlson@gmail.com
Now even awesomer
function entireWorkflow(callback) {
fs.readdir('/some/path', function (err, fileNames) {
// Often forgotten or ignored is handling err
if (err) throw new Error("We're screwed");
fileNames.forEach(function (name) {
fs.readFile(name, function (fileErr, file) {
// Especially when doing nested or more complex flows
if (fileErr) throw new Error("This is getting seriously annoying");
// Insert value-adding code here
});
});
// Uhh... when do I call `callback()`?
});
}
function asyncStuff() {
var dfd = Q.defer();
dfd.resolve(10);
// or
dfd.reject(new Error('Reason'));
return dfd.promise;
}
Revealing constructor pattern:
new Promise((resolve, reject) => {
resolve(10);
// or
reject(new Error('Reason'));
});
p instanceof Promise;
p.constructor === Promise;
Object.getPrototypeOf(p) === Promise.prototype;
Now possible:
Promise.resolve('A')
.then(a => $.Deferred().resolve(a + 'B').promise())
.then(b => Q(b + 'C'))
.then(c => console.log(c)) // ABC
.catch(handleTheError)
window.crypto.subtle.generateKey(...)
.then(function(key){
console.log(key);
})
.catch(function(err){
console.error(err);
});
try {
let topPost = getTopRedditPost();
console.log("Top Post: ", topPost.title);
let topComment = getTopComment(topPost);
console.log("Top Comment: ", topComment.body);
}
catch(err) {
console.log("Error: " + err);
}
function GET(url, cb) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.send();
req.onreadystatechange = function () {
if (req.readyState === 4) {
if (req.status === 200) {
cb(null, JSON.parse(req.responseText));
} else {
cb(new Error(`Failed Request with Status: ${req.status}`));
}
}
};
}
GET('https://www.reddit.com/r/javascript/top.json', function (err, posts) {
if (err) console.log(`Problem: ${err.message}`);
var topPost = posts.data.children[0].data;
console.log(`Top Post: ${topPost.title}`);
GET(`https://www.reddit.com/r/javascript/comments/${topPost.id}.json`, function (err, comments) {
if (err) console.log(`Problem: ${err.message}`);
var topComment = comments[1].data.children[0].data;
console.log(`Top Comment: ${topComment.body}`);
});
});
getTopRedditPost();
.then(topPost => {
console.log(`Top Post: ${topPost.title}`);
return getTopComment(topPost)
.then(topComment => {
console.log(`Top Comment: ${topComment.body}`);
});
})
.catch(err => console.log(`Error: ${err.message}`));
function getTopRedditPost() {
return GET('https://www.reddit.com/r/javascript/top.json')
.then(posts => posts.data.children[0].data);
}
function getTopComment(post) {
return GET(`https://www.reddit.com
/r/javascript/comments/${topPost.id}.json`)
.then(comments => comments[1].data.children[0].data);
}
try {
var topPost = getTopRedditPost();
console.log("Top Post: ", topPost.title);
var topComment = getTopComment(topPost);
console.log("Top Comment: ", topComment.body);
}
catch(err) {
console.log("Error: " + err);
}
(if you weren't already)
let namePromises = [
GETPROFILE('sally'),
GETPROFILE('bob'),
GETPROFILE('sue')
];
Promise.all(names)
.then(profiles => console.log(profiles.length)); // 3
// OR written more realistically with Map
let names = ['sally', 'bob', 'sue'];
Promise.all(names.map(name => GETPROFILE(name)))
.then(profiles => console.log(profiles.length)); // 3
try {
let top5Posts = getTop5RedditPosts();
for (let post of top5Posts) {
console.log('Top Post:', post.title);
let topComment = getTopComment(post);
console.log('Top Comment:', topComment.body);
}
}
catch(err) {
console.log('Error:', err);
}
getTop5RedditPosts()
.then(topFivePosts => {
return Promise.all(topFivePosts.map(post => {
return getPostTopComment(post)
.then(topComment => {
console.log(`----\nTop Post: ${post.data.title}`);
console.log(`Top Comment: ${topComment.body}`);
});
}));
})
.catch(err => console.log(`Error: ${err.message}`));
function getTop5RedditPosts() {
return GET('https://www.reddit.com/r/javascript/top.json')
.then(posts => posts.data.children.slice(0, 5));
}
function getTopComment(post) {
return GET(`https://www.reddit.com
/r/javascript/comments/${topPost.id}.json`)
.then(comments => comments[1].data.children[0].data);
}
try {
let top5Posts = getTop5RedditPosts();
for (let post of top5Posts) {
console.log('Top Post:', topPost.title);
let topComment = getTopComment(topPost);
console.log('Top Comment:', topComment.body);
}
}
catch(err) {
console.log('Error:', err);
}
iterface IIterable {
[Symbol.iterator](): IIterator
}
interface IIterator {
next(): IIteratorResult,
throw()?: IIteratorResult
return()?: IIteratorResult
}
interface IIteratorResult {
value: T,
done: boolean
}
let arr = ['a', 'b'];
for (let pair of arr.entries()) {
console.log(pair); // [0, 'a'] [1, 'b']
}
let arr = ['a', 'b', 'c'];
let itr = arr.entries();
itr.next(); // {"value":[0,"a"],"done":false}
itr.next(); // {"value":[1,"b"],"done":false}
itr.next(); // {"value":[2,"c"],"done":false}
itr.next(); // {"done":true}
let msg = "hi";
let iterator = msg[Symbol.iterator]();
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
Use the function* syntax
function* producer() {
yield 1;
for (let i = 2; i <= 4; i++) {
yield i;
}
return 5;
}
var iter = producer();
Array.from(iter); // ???
var iter = producer();
iter.next() // { done: false, value: 1 }
iter.next() // { done: false, value: 2 }
iter.next() // { done: false, value: 3 }
iter.next() // { done: false, value: 4 }
iter.next() // { done: true, value: 5 }
var iter = producer();
Array.from(iter); // [1,2,3,4] WAT - Iteration doesn't include return
Yield pauses, returns yielded value to the consumer
function* producer() {
yield 1;
}
producer().next(); // { value: 1, done: false }
Next() continues execution and takes an optional value
function* producer() {
let a = yield 1;
return a + 1;
}
let p = producer();
p.next(); // { value: 1, done: false }
p.next(2); // { value: 3, done: true }
var producer = function*() {
let a = yield 10;
console.log('A', a);
let b = yield a;
console.log('B', b);
return 30;
}
var iter = producer();
console.log('yielded', iter.next());
console.log('yielded', iter.next());
console.log('yielded', iter.next(100));
console.log('yielded', iter.next(200));
yielded {"value":10,"done":false}
A Undefined
yielded {"done":false}
B 100
yielded {"value":30,"done":true}
yielded {"done":true}
Spawn takes a generator function. Every time the generator yields a promise, the execution of the generator is halted until the promise is resolved at which time spawn will call next() with the promise's resolved value, on the generator.
function spawn(gen) {
let iter = gen();
let p1 = iter.next().value;
let p2 = p1.then(res => iter.next(res).value);
return p2;
}
spawn(function*(){
let a = yield Promise.resolve(10);
return a + 20;
})
.then(res => console.log(res)); // 30
Rework our same scenario to use an iterator function, yield, and spawn.
(spawn(function*(){
try {
let posts = yield GET('https://www.reddit.com/r/javascript/top.json');
let topFivePosts = posts.data.children.slice(0, 5);
for (let post of topFivePosts) {
let comments = yield GET(`https://www.reddit.com/r/javascript/comments/${post.data.id}.json`);
let topComment = comments[1].data.children[0].data;
console.log(`----\nTop Post: ${post.data.title}`);
console.log(`Top Comment: ${topComment.body}`);
}
} catch (ex) {
console.log(`Error: ${ex.message}`);
}
}))();
Q - .spawn()
Bluebird - .coroutine()
let fetchTop5 = async function() {
try {
let top5Posts = await getTop5RedditPosts();
for (let post of topFivePosts) {
console.log(`----\nTop Post: ${post.data.title}`);
let comment = await getTopComment(post);
console.log(`Top Comment: ${topComment.body}`);
}
} catch (ex) {
console.log('Error:', err);
}
};
fetchTop5();
function getTop5RedditPosts() {
return GET('https://www.reddit.com/r/javascript/top.json')
.then(posts => posts.data.children.slice(0, 5));
}
function getTopComment(post) {
return GET(`https://www.reddit.com
/r/javascript/comments/${topPost.id}.json`)
.then(comments => comments[1].data.children[0].data);
}
let fetchTop5 = async function() {
let posts = await GET('https://www.reddit.com/r/javascript/top.json');
let topFivePosts = posts.data.children.slice(0, 5);
for (let post of topFivePosts) {
let comments = await GET(`https://www.reddit.com/r/javascript/comments/${post.data.id}.json`);
let topComment = comments[1].data.children[0].data;
console.log(`----\nTop Post: ${post.data.title}`);
console.log(`Top Comment: ${topComment.body}`);
}
return 'All done!'; // Here's that return value that was ignored
};
let fetching = fetchTop5()
.then(res => console.log(res));
Async functions return a promise.
The resolved value is the value returned.
(if you weren't already)
@adamterlson
adam.terlson@gmail.com
By Adam Terlson
Learn about the changes in ES6 and ES7 that will make async code radically different. Introduces the concept of iterators, generators and walks slowly into async functions. Contains a host of solid, incremental examples.