Getting some urls for content, then fetching that content & adding it to the page sequentially. Promises vs node style callbacks vs events.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// With Promises | |
getJson('story.json').then(function(story) { | |
addHtmlToPage(story.heading); | |
// Map our array of chapter urls | |
// to an array of chapter json promises | |
return story.chapterUrls.map(getJson).reduce(function(chain, chapterPromise) { | |
// Use reduce to chain the promises together, | |
// but adding content to the page for each chapter | |
return chain.then(function() { | |
return chapterPromise; | |
}).then(function(chapter) { | |
addHtmlToPage(chapter.html); | |
}); | |
}, Promise.resolve()); | |
}).then(function() { | |
addTextToPage("All done"); | |
}).catch(function(err) { | |
// catch any error that happened along the way | |
addTextToPage("Argh, broken: " + err.message); | |
}).then(function() { | |
document.querySelector('.spinner').style.display = 'none'; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// With Node.js-style callbacks | |
// A couple of functions to save repetiton | |
function handleError(err) { | |
addTextToPage("Argh, broken: " + err.message); | |
hideSpinner(); | |
} | |
function hideSpinner() { | |
document.querySelector('.spinner').style.display = 'none'; | |
} | |
getJsonCallback('story.json', function(err, story) { | |
// In Node.js callbacks, the first param is an error | |
if (err) { | |
handleError(err); | |
return; | |
} | |
addHtmlToPage(story.heading); | |
// We need to track if anything failed | |
var errored = false; | |
// Track the next chapter that can appear on the page | |
var nextChapterToAdd = 0; | |
var chapterHtmls = []; | |
story.chapterUrls.forEach(function(chapterUrl, i) { | |
getJsonCallback(chapterUrl, function(err, chapter) { | |
// If we've already errored, don't want to do anything | |
if (errored) { | |
return; | |
} | |
// If this is an error, handle it | |
if (err) { | |
errored = true; | |
handleError(err); | |
return; | |
} | |
// Store this chapter, we might not be able to deal with it yet | |
chapterHtmls[i] = chapter.html; | |
// Try and add new chapters to the page, in order. | |
// Stop when we reach a chapter we've yet to download. | |
for (; nextChapterToAdd in chapterHtmls; nextChapterToAdd++) { | |
addHtmlToPage(chapterHtmls[nextChapterToAdd]); | |
// Did we just add the final chapter? | |
if (nextChapterToAdd == story.chapterUrls.length - 1) { | |
addTextToPage("All done"); | |
hideSpinner(); | |
} | |
} | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// With events (using simpler on* properties) | |
// A couple of functions to save repetiton | |
function handleError(err) { | |
addTextToPage("Argh, broken: " + err.message); | |
hideSpinner(); | |
} | |
function hideSpinner() { | |
document.querySelector('.spinner').style.display = 'none'; | |
} | |
// In this example, we get an object we add load & error listeners to | |
var request = getJsonEvent('story.json'); | |
request.onload = function() { | |
addHtmlToPage(request.response.heading); | |
// We need to track if anything failed | |
var errored = false; | |
// Track the next chapter that can appear on the page | |
var nextChapterToAdd = 0; | |
var chapterHtmls = []; | |
// We store chapter requests so we can remove listeners later | |
var chapterRequests = request.response.chapterUrls.map(function(chapterUrl, i) { | |
var request = getJsonEvent(chapterUrl); | |
request.onload = function() { | |
// Store this chapter, we might not be able to deal with it yet | |
chapterHtmls[i] = request.response.html; | |
// Try and add new chapters to the page, in order. | |
// Stop when we reach a chapter we've yet to download. | |
for (; nextChapterToAdd in chapterHtmls; nextChapterToAdd++) { | |
addHtmlToPage(chapterHtmls[nextChapterToAdd]); | |
// Did we just add the final chapter? | |
if (nextChapterToAdd == story.chapterUrls.length - 1) { | |
addTextToPage("All done"); | |
hideSpinner(); | |
} | |
} | |
}; | |
request.onerror = function(event) { | |
// prevent further events | |
chapterRequests.forEach(function(request) { | |
request.onload = null; | |
request.onerror = null; | |
}); | |
handleError(event.error); | |
}; | |
return request; | |
}); | |
}; | |
request.onerror = function(event) { | |
handleError(event.error); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// With Promises + Generators (and the spawn helper) | |
spawn(function *() { | |
try { | |
// 'yield' effectively does an async wait, returning the result of the promise | |
let story = yield getJson('story.json'); | |
addHtmlToPage(story.heading); | |
// Map our array of chapter urls | |
// to an array of chapter json promises. | |
// This makes sure they all download parallel. | |
let chapterPromises = story.chapterUrls.map(getJson); | |
// Can't use chapterPromises.forEach, because yielding within doesn't work | |
for (let chapterPromise of chapterPromises) { | |
// Wait for each chapter to be ready, then add it to the page | |
let chapter = yield chapterPromise; | |
addHtmlToPage(chapter.html); | |
} | |
addTextToPage("All done"); | |
} | |
catch (err) { | |
// try/catch just works, rejected promises are thrown here | |
addTextToPage("Argh, broken: " + err.message); | |
} | |
document.querySelector('.spinner').style.display = 'none'; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Update:
Realized another way to do the promises-style example, without needing reduce, but instead another
map(..)
call, plusapply(..)
. This feels a bit more readable IMO:Here's how it works:
getJson("story.json")
fires off a request to get the JSON for the story.seq(function(story){...
runs, wherestory
is the JSON object.addHtmlToPage(story.heading)
renders the story heading to the page.story.chapterUrls
is an array of strings (URLs).story.chapterUrls.map(getJson)
callsgetJson()
for each URL string in the original array, which produces a new array of loading sequences, one for each chapter URL.story.chapterUrls.map(getJson).map(waitForChapter)
callswaitForChapter()
for each loading-chapter sequence, which returns a function that will generate a new sequence which listens for each chapter loading sequence to finish and then callsrenderChapter()
to render that newly loaded chapter. The result of this secondmap(..)
call is yet another new array, a series of functions which, when called, each produce a load-and-render sequence for each chapter.ASQ().seq.apply(null, ... )
takes this array of functions andapply
s it toASQ.seq()
, which chains each chapter load-and-render sequence as sequential steps in this main sequence. So each chapter sequence will, when the previous chapter finishes rendering, check to see if the next chapter is loaded, and if so, render the it, otherwise will keep waiting.The end-result of this map/map/apply trick is basically the same as: