at its root, a promise is an object to which you attach callbacks. these callbacks happen once the 'value' property changes.
example with callback
function successCallback(result) {
console.log('Audio file ready at URL: ' + result);
}
function failureCallback(error) {
console.error('Error generating audio file: ' + error);
}
createAudioFileAsync(audioSettings, successCallback, failureCallback);
example with promise
createAudioFileAsync(audioSettings).then(successCallback, failureCallback);
what makes a promise better than a callback?
- unlike passed-in callbacks (above), callbacks will never be called before the completion of the current run of the JS event loop. AKA the callbacks will wait their turn until the function/promise has resolved before firing.
- callbacks added with
then()
even after the success or failure of the async operation, will be called. - multiple callbacks may be added by calling
then()
several times.
when we run .then()
on a promise, the `then() function returns a new promise, different from the original.
ex:
const promise2 = doSomething().then(successCallback, failureCallback);
^ the second promise represents the completion not just of doSomething()
but also successCallback
or failureCallback
you passed in, which can be other async fns returning a promise. when thats the case, any callbacks added to promise2
get queued behind the promise returned by either successCallback
or `failureCallback.
you can chain after a failure aka a catch
which is good to do stuff even after a promise chain has failed:
new Promise((resolve, reject) => {
console.log('initial');
resolve();
})
.then(() => {
throw new Error('somethin failed');
console.log('do this');
})
.catch(() => {
console.error('do that (err)')
})
.then(() => {
console.log('no matter what happened before, do this);
});
/*
Initial
Do that (err)
Do this, no matter what happened before
*/
in a standard callback pyramid of hell, you'd see failureCallback
multiple times like:
doSomething(function(result) {
doSomethingElse(
result,
function(newResult) {
doThirdThing(
newResult,
function(finalResult) {
console.log('Got the final result: ' + finalResult);
},
failureCallback
);
},
failureCallback
);
}, failureCallback);
though in a promise you only see it once? why?
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);
so if there's a problem, the promise chain will be looking for a catch handler and handle it there. this is super related to regs synchronous code:
try {
const result = syncDoSomething();
const newResult = syncDoSomethingElse(result);
const finalResult = syncDoThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch (error) {
failureCallback(error);
}
you can create a promise from scratch using its constructor. this should be only needed to wrap old APIs. ideally, all async fns would already return promises. but some APIs still expect sucess/failure callbacks the old school way.
ex: (remember sa)
setTimeout(() => saySomething('10 seconds passed'), 10 * 1000);
if something fucks up in saySomething()
, nothing acknowledges or catches this. but we can wrap setTimeout in a promise.
const wait = ms => newPromise(resolve => setTimeout(resolve, ms));
wait(10 * 1000)
.then(saySomething('10 secs passed'))
.catch(failureCallback);
prime example of JS adding a promise .then() fn to the microtask queue which then checks via the event loop to make sure the callstack is empty:
Promise.resolve().then(() => console.log(2));
console.log(1);
promise.reject()
returns a promise object that is rejected with reason for promise fail.
function resolved(result) {
console.log('resolved');
}
function rejected(result) {
console.err(result);
}
Promise.reject(new Error('fail')).then(resolved, rejected);