On Github georgeh / promise-talk
George Hotelling - Progressive Leasing
spotifyApi.clientCredentialsGrant(function(err, credentials) { if (err) return err; spotifyApi.setAccessToken(credentials.body['access_token']); return spotifyApi.getPlaylist('spotify', 'NewMusicFriday', function postPlaylist(err, spotifyRes) { if (err) return err; return reddit('/api/submit').post({ 'api_type': 'json', 'kind': 'link', 'resubmit': true, 'sendreplies': false, 'sr': config.reddit.subreddit, 'title': _.get(spotifyRes, 'body.name') + ' - ' + moment().format('MMMM Do, YYYY'), 'url': _.get(spotifyRes, 'body.external_urls.spotify') }, function(err, redditPostRes) { if (err) return err; return reddit('/api/set_subreddit_sticky').post({ id: redditPostRes.json.data.name, num: 1, state: true }, function(err) { if (err) return err; var trackPosted = [].fill(false, 0, spotifyRes.body.tracks.items.length - 1); for (var i=0; i < spotifyRes.body.tracks.items.length; i++) { var item = spotifyRes.body.tracks.items[i]; var artists = item.track.artists.map((a) => a.name).join(', '); var title = artists + ' - ' + item.track.name; reddit('/api/comment').post({ 'api_type': 'json', 'text': '[' + title + '](' + _.get(item, 'track.external_urls.spotify') + ')', 'thing_id': spotifyRes.reddit.json.data.name }, function(err) { if (err) return err; console.log('Posted ' + title); trackPosted[i] = true; if (trackPosted.every((trackDone) => trackDone)) { console.log('DONE!'); } }); } }); }); }); });This code is for illustration purposes only and probably wouldn't work even if you could find an API that supported callbacks.
spotifyApi.clientCredentialsGrant(function (err, credentials) { if (err) return err; spotifyApi.setAccessToken(credentials.body['access_token']); return spotifyApi.getPlaylist('spotify', 'NewMusicFriday', postPlaylist); }); var spotifyRes; function postPlaylist(err, spotifyResArg) { if (err) return err; spotifyRes = spotifyResArg; return reddit('/api/submit').post({ 'api_type': 'json', 'kind': 'link', 'resubmit': true, 'sendreplies': false, 'sr': config.reddit.subreddit, 'title': _.get(spotifyRes, 'body.name') + ' - ' + moment().format('MMMM Do, YYYY'), 'url': _.get(spotifyRes, 'body.external_urls.spotify') }, stickyPlaylist); } function stickyPlaylist(err, redditPostRes) { if (err) return err; return reddit('/api/set_subreddit_sticky').post({ id: redditPostRes.json.data.name, num: 1, state: true }, postTracks); } var trackPosted; function postTracks(err) { if (err) return err; trackPosted = [].fill(false, 0, spotifyRes.body.tracks.items.length - 1); for (var i = 0; i < spotifyRes.body.tracks.items.length; i++) { var item = spotifyRes.body.tracks.items[i]; var artists = item.track.artists.map((a) => a.name).join(', '); var title = artists + ' - ' + item.track.name; reddit('/api/comment').post({ 'api_type': 'json', 'text': '[' + title + '](' + _.get(item, 'track.external_urls.spotify') + ')', 'thing_id': spotifyRes.reddit.json.data.name }, onTrackPosted(title, i)); } } function onTrackPosted(title, i) { return function (err) { if (err) return err; console.log('Posted ' + title); trackPosted[i] = true; if (trackPosted.every((trackDone) => trackDone)) { console.log('DONE!'); } } }Still bad code, just not as ugly. Highly coupled, brittle.
It is a Promise for a value. Eventually.
For when you want to pass around a value you don't have yet.
var promise = new Promise(function(resolve, reject) { try { … resolve(value); } catch(e) { reject(e); } }));
var resolvedPromise = Promise.resolve(foo);
var rejectededPromise = Promise.reject(bar);
spotifyApi.clientCredentialsGrant() .then(function(credentials) { spotifyApi.setAccessToken(credentials.body['access_token']); spotifyApi.getPlaylist('spotify', 'NewMusicFriday'); })
spotifyApi.clientCredentialsGrant() .then(function(credentials) { spotifyApi.setAccessToken(credentials.body['access_token']); spotifyApi.getPlaylist('spotify', 'NewMusicFriday') .then(function (playlist) { reddit('/api/submit').post({ 'api_type': 'json', 'kind': 'link', 'resubmit': true, 'sendreplies': false, 'sr': 'NewMusicFriday', 'title': _.get(playlist, 'body.name') + ' - ' + moment().format('MMMM Do, YYYY'), 'url': _.get(playlist, 'body.external_urls.spotify') }); }); });You can see the Pyramid of Doom growing
Calling .then() inside .then()
var playlistPromise = new Promise(function(resolve, reject) { spotifyApi.clientCredentialsGrant() .then(function(credentials) { spotifyApi.setAccessToken(credentials.body['access_token']); spotifyApi.getPlaylist('spotify', 'NewMusicFriday') .then(resolve) }) }); playlistPromise.then(function(playlist) { reddit('/api/submit').post({ … 'sr': 'NewMusicFriday', 'title': _.get(playlist, 'body.name') + ' - ' + moment().format('MMMM Do, YYYY'), 'url': _.get(playlist, 'body.external_urls.spotify') }) });
You (almost) never need to create a new, pending Promise!
Promise.resolve() and Promise.reject() are OK
Exception is when you are converting non-Promise async code to PromisesIf you return a Promise from a .then() the outer Promise becomes the inner Promise
That means that the chain will become unresolved and will wait to call .then()spotifyApi.clientCredentialsGrant() .then(function(credentials) { spotifyApi.setAccessToken(credentials.body['access_token']); return spotifyApi.getPlaylist('spotify', 'NewMusicFriday'); }) .then(function(playlist) { reddit('/api/submit').post({ 'api_type': 'json', 'kind': 'link', 'resubmit': true, 'sendreplies': false, 'sr': 'NewMusicFriday', 'title': _.get(playlist, 'body.name') + ' - ' + moment().format('MMMM Do, YYYY'), 'url': _.get(playlist, 'body.external_urls.spotify') }) })
var credentialPromise = spotifyApi.clientCredentialsGrant(); var playlistPromise = credentialPromise.then(function(credentials) { spotifyApi.setAccessToken(credentials.body['access_token']); return spotifyApi.getPlaylist('spotify', 'NewMusicFriday'); }); var redditPostPromise = playlistPromise.then(function(playlist) { reddit('/api/submit').post({ 'api_type': 'json', 'kind': 'link', 'resubmit': true, 'sendreplies': false, 'sr': 'NewMusicFriday', 'title': _.get(playlist, 'body.name') + ' - ' + moment().format('MMMM Do, YYYY'), 'url': _.get(playlist, 'body.external_urls.spotify') }) });This is the same code as the last slide with the explicit Promises broken out.
If you return nothing from a .then(), the Promise value stays the same.
spotifyApi.clientCredentialsGrant() .then(saveAccessToken) .then(getPlaylist) .then(postPlaylist) function saveAccessToken(credentials) { spotifyApi.setAccessToken(credentials.body['access_token']); } function getPlaylist() { return spotifyApi.getPlaylist('spotify', 'NewMusicFriday'); } function postPlaylist(playlist) { return reddit('/api/submit').post({…}); }
spotifyApi.clientCredentialsGrant() .then(saveAccessToken) .then(getPlaylist) .then(postPlaylist) .then(stickyPlaylist) … function stickyPlaylist(redditPostRes) { return reddit('/api/set_subreddit_sticky').post({ id: redditPostRes.json.data.name, num: 1, state: true }); }
spotifyApi.clientCredentialsGrant() .then(saveAccessToken) .then(getPlaylist) .then(postPlaylist) .then(stickyPlaylist) .then(postTracks) … function postTracks(???) { }What are the arguments here? We need the result of getPlaylist() and postPlaylist()!
spotifyApi.clientCredentialsGrant() .then(saveAccessToken) .then(getPlaylist) .then(postPlaylist) .then(stickyPlaylist) … function postPlaylist(playlist) { return Promise.all([playlist, reddit('/api/submit').post({…})]); } function stickyPlaylist(playlist, redditPost) { return Promise.all([playlist, redditPost, reddit('/api/set_subreddit_sticky').post({…})]); }We are now using Promise.all() to collect the results we need
spotifyApi.clientCredentialsGrant() .then(saveAccessToken) .then(getPlaylist) .then(postPlaylist) .then(stickyPlaylist) .then(postTracks) … function postTracks(playlist, redditPost) { return Promise.all(playlist.body.tracks.items.map(function(item) { var artists = item.track.artists.map((a) => a.name).join(', '); var title = artists + ' - ' + item.track.name; return reddit('/api/comment').post({ 'api_type': 'json', 'text': '[' + title + '](' + _.get(item, 'track.external_urls.spotify') + ')', 'thing_id': redditPost.json.data.name }) .then(function() { console.log('Posted ' + title); }); })); }Remember Anti-Pattern #1 - calling a .then() from inside .then()? Not applicable here because waves hands and trails off Real reason - we could attach another method after postTracks(), or can consider the Track -> Promise map to be its own Promise chain.
spotifyApi.clientCredentialsGrant(function (err, credentials) { if (err) return err; … }); function postPlaylist(err, spotifyResArg) { if (err) return err; … } function stickyPlaylist(err, redditPostRes) { if (err) return err; … } function postTracks(err) { if (err) return err; … } function onTrackPosted(title, i) { return function (err) { if (err) return err; … } } }
spotifyApi.clientCredentialsGrant() .then(saveAccessToken) .then(getPlaylist) .then(postPlaylist) .then(stickyPlaylist) .then(postTracks) .then(console.log.bind(console, 'DONE!')) .catch(console.error.bind(console))
function postTracks(playlist, redditPost) { return Promise.all(playlist.body.tracks.items.map(function(item) { var artists = item.track.artists.map((a) => a.name).join(', '); if (/bieber/i.test(artists)) { throw 'Not a Belieber!'; } var title = artists + ' - ' + item.track.name; return reddit('/api/comment').post({ 'api_type': 'json', 'text': '[' + title + '](' + _.get(item, 'track.external_urls.spotify') + ')', 'thing_id': redditPost.json.data.name }) .then(function() { console.log('Posted ' + title); }); })); }This is the Promise version of a throw statement
Not having a .catch() statement.
try { let credentials = await spotifyApi.clientCredentialsGrant(); spotifyApi.setAccessToken(credentials.body['access_token']); let playlist = await getPlaylist(); let redditPost = await postPlaylist(playlist); await Promise.all([ stickyPlaylist(playlist, redditPost), postTracks(playlist, redditPost) ]); console.log('DONE!'); } catch (err) { console.error(err); } async function postPlaylist(playlist) { return reddit('/api/submit').post({…}); }Note the use of Promise.all() - it's important to understand that these are all Promises under the hood
Slides up on https://github.com/georgeh/
Ask in the #javascript room in Slack