Created
November 20, 2014 09:31
-
-
Save dinks/a724ad6816ab924b03e3 to your computer and use it in GitHub Desktop.
Promise Patterns
This file contains hidden or 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
| // https://remysharp.com/2014/11/19/my-five-promise-patterns | |
| // Clean shallow chains | |
| var writeFile = Promise.denodeify(fs.writeFile); | |
| writeFile(filename, content) | |
| .then(addDBUser) | |
| .then(dns) | |
| .then(configureHeroku) | |
| .then(function () { | |
| console.log('All done'); | |
| }); | |
| // If the method relies on Method Context.. | |
| var addUser = Promise | |
| .denodeify(model.user.add) | |
| .bind(model.user); | |
| // Prebaking | |
| writeFile(filename, content) | |
| .then(function () { | |
| return addUserToDb('rem', 'password', 'some-db'); | |
| }); | |
| // this becomes | |
| var addUser = addUserToDb.bind(null, 'rem', | |
| 'password', 'some-db'); | |
| writeFile(filename, content) | |
| .then(addUser); | |
| // Cold calling | |
| // When a function works both as a promise and using the callback pattern - it's great, but.. | |
| function configureHeroku(slug) { | |
| // prebake heroku app create promise | |
| var create = heroku.post.bind(heroku, | |
| '/apps', | |
| { name: 'example-' + slug } | |
| ); | |
| // prebake domain config | |
| var domain = heroku.post.bind(heroku, | |
| '/apps/example-' + slug + '/domains', | |
| { hostname: slug + '.example.com' } | |
| ); | |
| // ** this is where it goes wrong ** // | |
| return create().then(domain); | |
| }; | |
| // The issue is when domain is called, it's actually called with the prebaked arguments of the slug and options | |
| // but also the resolved value from create() - so a third argument is received. | |
| // This third argument is the resolved result of create() which is treated as the callback argument | |
| // and as a function object, so the code will try to invoke it - causing an exception. | |
| function coldcall(fn) { | |
| return function () { | |
| fn(); | |
| }; | |
| } | |
| function configureHeroku(slug) { | |
| // ... | |
| // ** now it works ** // | |
| return create().then(coldcall(domain)); | |
| } | |
| // Throw over explicit reject | |
| return new Promise(function (resolve, reject) { | |
| bcrypt.compare(input, password, function (error, result) { | |
| if (error || !result) { | |
| // reject and early exit | |
| return reject(error); | |
| } | |
| resolve(result); | |
| }); | |
| }); | |
| // becomes | |
| return new Promise(function (resolve) { | |
| bcrypt.compare(input, password, function (error, result) { | |
| if (error) { | |
| throw error; | |
| } | |
| if (!result) { | |
| throw new Error('Passwords did not match.'); | |
| } | |
| resolve(result); | |
| }); | |
| }); | |
| // reject is supposed to be analogous to throw but async. So reject what you'd throw (which is usually an error) | |
| // post with "in ES7 async functions reject is throw". | |
| // This also reinforces that you want to reject with a real error, not a string. | |
| // Always end with a catch | |
| writeFile(filename, content) | |
| .then(addDBUser) | |
| .then(dns) | |
| .then(configureHeroku) | |
| .then(function () { | |
| console.log('All done'); | |
| }) | |
| .catch(function (error) { | |
| // do something with error | |
| console.log(error.stack); | |
| handle(error); | |
| }); | |
| // Note: .catch() is only in the ES6 spec and doesn't appear in Promises/A+ | |
| // so some library implementations are missing .catch() support |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment