Asynchronous and Synchronous are two behaviours, in programming terms asynchronous was deemed the more problematic as the delay for something to return was indeterminate from may be milliseconds or even seconds, synchronous on the other hand simply waited until data was returned however this mean't that the problem paused.
In the past Javascript used timeouts to mimic asychronous programming, also you used to use try-catch blocks to handle any issues or errors, this type of programming was called the callback from hell and was not scaleable. The new feature to fully support asynchronous programming was called promises.
A promise is an object that may produce a single value some time in the future, the retruned data may be what you requested or an error could occur (a network error occurred, database error). A promise may be in one of 3 possible states, fulfilled, rejected, or pending. You can attach callbacks to a promise to handle the returned data or handle the failure.
So why use a promise, firstly its to abstract the block of asynchronous code execution, to allow the program to continue on its merry way, for example you may want to retrieve so data from an external source, you can do this with a promise and still continue creating the web page while we wait for the data to be returned to finish off the web page. Also because the state of the promise is private you can manipulate the data returned before displaying it on the web page.
The promise execution has two primary duties initialize the asynchronous of the promiseand controlling any state transition. One either function is executed there is no going back to the pending state, the two functions used are resolve and reject, also yo avoid the promise getting stuck you add a timed exit.
Promise example | let p = time => new Promise((resolve, reject) => setTimeout(resolve, time)); p(2000).then( () => console.log('Hello!')); |
You can directly call either the resolve or reject
Resolve and Reject | setTimeout(console.log, 10, Promise.resolve(func1())); setTimeout(console.log, 0, Promise.reject(error())) |
The promise type implements a thenable interface which you can attach handlers, the then() method is used to attach handlers and it accepts two arguments an optional onResolved handler and an optional onRejected handler.
then() method | function onResolved(id) { setTimeout(console.log, 0, id, 'resolved'); } function onRejected(id) { setTimeout(console.log, 0, id, 'rejected'); } let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000)); let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000)); p1.then(() => onResolved('p1'), () => onRejected('p1')); p2.then(() => onResolved('p2'), () => onRejected('p2')); |
then() method (optional resolve/reject) | function onResolved(id) { setTimeout(console.log, 0, id, 'resolved'); } function onRejected(id) { setTimeout(console.log, 0, id, 'rejected'); } let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000)); let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000)); // Non-function handlers are silently ignored, not recommended p1.then(() => onResolved('p1'), null); // Canonical form of explicit onResolved handler skipping p2.then(null, () => onRejected('p2')); |
Explicit return values | let p1 = Promise.resolve('foo'); setTimeout(console.log, 0, p1); let p2 = p1.then(() => 'bar'); // new onResolved handler setTimeout(console.log, 0, p2); |
catch() method | let p = Promise.reject(); let onRejected = function(e) { setTimeout(console.log, 0, 'rejected'); }; // These two reject handlers behave identically: p.then(null, onRejected); // rejected p.catch(onRejected); // rejected Note: catch() method can be used to attach only a reject handler to a promise |
finally() method | let p1 = Promise.resolve(); let p2 = Promise.reject(); let onFinally = function() { setTimeout(console.log, 0, 'Finally!') } p1.finally(onFinally); // Finally p2.finally(onFinally); // Finally Note: finally() method can be used to attach an OnFinally handler which executes when a promise reaches either a resolved or rejected state. |
You can combine multiple promises together which is called chaining, promises are strictly sequenced, each of the promises instance methods then(), catch() and finally() returns a separate promise instance which in turn can have another instance method called upon it.
Promise chaining (basic example) | let p = new Promise((resolve, reject) => { console.log('first'); resolve(); }); p.then(() => console.log('second')) // onResolved defined .then(() => console.log('third')) .then(() => console.log('fourth')); |
Promise chaining (more advanced example) | function delayedResolve(str) { return new Promise((resolve, reject) => { console.log(str); setTimeout(resolve, 1000); }); } delayedResolve('p1 executor') .then(() => delayedResolve('p2 executor')) .then(() => delayedResolve('p3 executor')) .then(() => delayedResolve('p4 executor')) |
Putting it all together | function func1() { console.log("Success"); } new Promise(() => func1()) .then(val => console.log(val)) // success .catch(err => console.log("Failed!")) .finally(() => console.log("Promise complete!")) // Promise complete! ----------------------------------------------------------------------- new Promise(() => undefined.get()) .then(val => console.log(val)) .catch(err => console.log("Failed!")) // Failed! .finally(() => console.log("Promise complete!")) // Promise complete! |
You can also use advanced parallel features such as all() and race() methods.
Async is normally paired with wait and are ES7 (and above) promise paradigm, you use the keyword async which can be used on functions declarations, function expressions, arrow functions and methods.
Async keyword | async function foo() {} let bar = async function() {}; let baz = async () => {}; class Qux { async qux() {} } |
Async has some asynchronous characteristics but its still synchronously evaluated, when used with a function a promised is returned and the return statement is the Promise.resolve().
Async and Function | async function hello() { console.log("1"); return 3; // this is the Promise.resolve() } hello().then(console.log); // a promise is returned console.log(2); |
The awaits keyword is used to pause the execution while awaiting for a promise to resolve.
awaits keyword | async function foo() { let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3)); console.log(p) // pending console.log(await p); // will wait until resolved console.log("Hello"); // will execute after above } foo(); // 3 console.log("World!") // will execute roughly after pending proofing asynchronous |
sleep function using async/awaits | async function sleep(delay) { return new Promise((resolve) => setTimeout(resolve, delay)); } async function foo() { let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 1)); console.log(await p); // will be displayed second (1 second wait) await sleep(5000); console.log("2"); // will be displayed last (7 second wait as above sleep) } foo(); console.log("3") // will be displayed first |
You can also use serial promise execution thus awaiting for a function to return before moing on
Serial promise execution | async function addTwo(x) {return x + 2;} async function addThree(x) {return x + 3;} async function addFive(x) {return x + 5;} async function addTen(x) { for (const fn of [addTwo, addThree, addFive]) { x = await fn(x); } return x; } addTen(9).then(console.log); // 19 |