Day 12: Add Two Promises
Given two promises promise1 and promise2, return a new promise. promise1 and promise2 will both resolve with a number. The returned promise should resolve with the sum of the two numbers.
Example 1:
Input:
promise1 = new Promise(resolve => setTimeout(() => resolve(2), 20)),
promise2 = new Promise(resolve => setTimeout(() => resolve(5), 60))
Output: 7
Explanation: The two input promises resolve with the values of 2 and 5 respectively. The returned promise should resolve with a value of 2 + 5 = 7. The time the returned promise resolves is not judged for this problem.
Example 2:
Input:
promise1 = new Promise(resolve => setTimeout(() => resolve(10), 50)),
promise2 = new Promise(resolve => setTimeout(() => resolve(-12), 30))
Output: -2
Explanation: The two input promises resolve with the values of 10 and -12 respectively. The returned promise should resolve with a value of 10 + -12 = -2.
Constraints:
promise1andpromise2are promises that resolve with a number
Overview
The problem requires creating a new promise that resolves with the sum of two numbers obtained from two given promises, promise1 and promise2.
Promises:
In Javascript promises represent the eventual completion (or failure) of an asynchronous operation and allow us to work with the results when they become available. They are a way to handle asynchronous code in a more organized and structured manner. Promises have three states: pending, fulfilled, or rejected.
- Pending: The initial state of a promise. It represents that the asynchronous operation is still ongoing and hasn't completed yet.
- Fulfilled: The state of a promise when the asynchronous operation is successfully completed. It means that the promised result or value is available.
- Rejected: The state of a promise when the asynchronous operation encounters an error or fails. It means that the promised result cannot be obtained.
Promises provide methods like.then()and.catch()to handle the resolved values or errors.
Whenever we use promises we usually come accross async/await keywords. Let's discuss those keywords also:
The async and await keywords are used to simplify working with promises and make asynchronous code appear more like synchronous code.
- async:
asyncis used to define an asynchronous function. It ensures that the function always returns a promise. When theasynckeyword is used before a function declaration or function expression, it becomes an asynchronous function.
Note: Non-promises returned from async functions are automatically wrapped in promises.
- await:
awaitis used to pause the execution of an asynchronous function until a promise is resolved. It can only be used inside anasyncfunction. Whenawaitis used before a promise, it waits for the promise to be resolved or rejected. If resolved it proceeds to the next line of code and if the awaited promise is rejected, an exception is thrown.
Usingawaitwithin anasyncfunction allows you to write asynchronous code in a more sequential and readable manner, without the need for explicit promise chaining using.then().
When a scenerio arises where you need to perform multiple asynchronous operations concurrently and wait for all of them to complete before proceeding. This is where the Promise.all() method is used prominently. Let's deep dive into it.
- The
Promise.all()method is used to handle multiple promises concurrently. It takes an array (or an iterable) of promises as input and returns a new promise that resolves when all the promises in the input array have resolved.
Note:
Promise.all()not necessarily only takes promises as input, it can also take just array of numbers and it'll resolve it - for example:
await Promise.all([1,2,Promise.resolve(3), Promise.resolve(4)]).then((value) => {
console.log(value)
}, (error) => {
console.log(error)
})
- The
Promise.all()method waits for all the promises to settle (eitherfulfilledorrejected). - If all the promises are
fulfilled, the returned promise isfulfilled, and the resolved values of the input promises are available as an array in the same order as the input promises. - If any of the promises are
rejected, the returned promise isrejectedwith the reason of the firstrejectedpromise. For example the below code will resolve with 'error', even though 3 other items resolved correctly.
await Promise.all([1, 2, Promise.resolve(3), Promise.reject('error')]).then(
value => {
console.log(value);
},
error => {
console.log(error);
}
);
- Using
Promise.all()allows for efficient parallel execution of multiple asynchronous operations and enables us to work with the combined results once they are all available. - For example when an application needs to fetch data from multiple APIs simultaneously,
Promise.all()can be used to initiate all the requests in parallel and wait for all the responses to arrive. Once all the promises are fulfilled, the application can process the combined data.
Approach 1: Using Promise.all
Intuition:
We can use Promise.all() to create a new promise that resolves when both promise1 and promise2 are resolved.
Algorithm:
- Inside the function, we use
Promise.all()to create a new promise that resolves when bothpromise1andpromise2are resolved.Promise.all()takes an array containing the promises as its argument. - With
Promise.all()we will useawaitkeyword to pause the execution of the function until the promise returned byPromise.all()is resolved. It waits for bothpromise1andpromise2to fulfill. - Once the promise returned by
Promise.all()is fulfilled, the resolved values ofpromise1andpromise2are available in an array. Using destructuring assignment, the values are assigned tores1andres2variables, respectively. - Finally, we return the sum of
res1andres2. Also we can put this entire logic in try catch block.
Implementation:
/**
* @param {Promise} promise1
* @param {Promise} promise2
* @return {Promise}
*/
var addTwoPromises = async function(promise1, promise2) {
try {
const [res1, res2] = await Promise.all([promise1, promise2]);
return res1 + res2;
} catch (error) {
console.error(error);
throw error; // Rethrow the error to maintain the behavior of propagating the error to the caller
}
};
Complexity Analysis:
The time complexity is determined based on the time of the promise that takes more time to resolve. That's because we have to wait for both of the promises to be fulfilled before returning the answer. Also, we don't need any extra space aside from the results, which should be returned and isn't counted in space complexity.
-
Time complexity: O(max(promise1,promise2))O(max(promise1, promise2))O(max(promise1,promise2))
-
Space complexity: O(1)O(1)O(1)
Approach 2: Using Only Await
Intuition:
The addTwoPromises function is defined as an async function, allowing us to use await inside it. The intuition behind this approach is to await the resolutions of promise1 and promise2 and then calculate the sum of their resolved values i.e., we can directly use await on the promises and return the sum.
Note: Use of await directly on
promise1andpromise2is not as concurrent as usingPromise.all(). As awaiting promises one after the other will result in a longer total time for resolution compared to awaiting them concurrently withPromise.all().
Algorithm:
- The function
addTwoPromisesis defined as an asynchronous function using theasynckeyword. This allows us to useawaitwithin the function to pause the execution until promises are resolved. - Inside the function, the
awaitkeyword is used directly onpromise1andpromise2. This pauses the execution of the function until the promises are resolved and the values are available. - Once the promises are resolved, we get their respective resolved values. Then the
awaitkeyword allows us to retrieve the resolved value of the promises. - Now the resolved values of
promise1andpromise2are added together using the+operator. - Finally, we can return a new promise that will be resolved with the sum of the resolved values of
promise1andpromise2.
Implementation:
/**
* @param {Promise} promise1
* @param {Promise} promise2
* @return {Promise}
*/
var addTwoPromises = async function(promise1, promise2) {
try {
return await promise1 + await promise2;
} catch (error) {
console.error(error);
throw error; // Rethrow the error to maintain the behavior of propagating the error to the caller
}
};
Complexity Analysis:
-
Time complexity: O(max(promise1,promise2))O(max(promise1, promise2))O(max(promise1,promise2))
-
Space complexity: O(1)
Approach 3: Using Promise.then()
Intuition:
We can use .then() to chain promises together and perform the addition of their resolved values i.e., first we can resolve promise1 and then promise2.
Algorithm:
- We use the
.then()method onpromise1. This allows us to chain the resolution ofpromise2afterpromise1is fulfilled. - Within the
.then()method's callback, the resolved value ofpromise1is represented asval. We will use another.then()method onpromise2to access its resolved value, represented asval2. - Inside the second
.then()callback, the values ofvalandval2are added together using the+operator. We can also use.catch()for error handling if any of the promises are rejected. - Finally, we return the result of the addition, which is implicitly wrapped in a promise due to the use of the
asynckeyword.
Implementation:
/**
* @param {Promise} promise1
* @param {Promise} promise2
* @return {Promise}
*/
var addTwoPromises = async function(promise1, promise2) {
try {
return promise1.then((val) => promise2.then((val2) => val + val2))
} catch (error) {
console.error(error);
throw error; // Rethrow the error to maintain the behavior of propagating the error to the caller
}
};
Complexity Analysis:
-
Time complexity: O(max(promise1,promise2))O(max(promise1, promise2))O(max(promise1,promise2))
-
Space complexity: O(1)
Approach 4: Count promises
Intuition:
We can handle the resolution of promise1 and promise2 in parallel and accumulate the results i.e., we can use a counter to keep track of the number of promises resolved, and once all promises are resolved, it resolves the new promise with the accumulated result.
Algorithm:
- Create two variables,
countandres, to keep track of the number of promises resolved and the accumulated result, respectively. - A
forEachloop to iterate over an array containingpromise1andpromise2. - For each promise in the loop, an async function is defined to handle the resolution.
- Inside the async function, the await keyword is used to pause the execution until the promise is resolved.
- If the promise is resolved successfully, the resolved value is stored in
subRes. Theresvariable is then updated by addingsubResto it, and thecountvariable is decremented by1. - After updating the variables, we check if the
countvariable is equal to0, indicating that both promises have been resolved. Ifcountis0, it means all promises are resolved, and the new promise is resolved with the accumulated result (res) using the resolve function. - If any promise is rejected, an error is caught, and the new promise is immediately rejected by calling the reject function.
Implementation:
/**
* @param {Promise} promise1
* @param {Promise} promise2
* @return {Promise}
*/
var addTwoPromises = async function (promise1, promise2) {
return new Promise((resolve, reject) => {
let count = 2;
let res = 0;
[promise1, promise2].forEach(async promise => {
try {
const subRes = await promise;
res += subRes;
count--;
if (count === 0) {
resolve(res);
}
} catch (err) {
reject(err);
}
});
});
};
Complexity Analysis:
-
Time complexity: O(promise1+promise2)O(promise1 + promise2)O(promise1+promise2)
-
Space complexity: O(1)
Interview Tips:
-
What is the purpose of the
Promise.all()method?- The
Promise.all()method is used to handle multiple promises concurrently. It takes an array of promises as input and returns a new promise. This new promise resolves when all the promises in the input array have resolved. The resolved values of the promises are available in the.then()block as an array in the same order as the input promises.
- The
-
How do you handle errors in promises?
- Errors in promises can be handled by using
try{} catch(err){} finallyblock to the promise chain.
- Errors in promises can be handled by using
-
What is the difference between synchronous and asynchronous operations?
- Synchronous operations block the execution of the program until the operation is complete. They are executed one after the other in a sequential manner. Asynchronous operations, on the other hand, do not block the execution of the program. They allow the program to continue executing other tasks while waiting for the operation to complete. Asynchronous operations are typically used for tasks that may take longer to complete, such as network requests or file operations, to avoid blocking the main execution thread.
-
What are the differences between callbacks and promises in JavaScript? When would you prefer using promises over callbacks? Also what is callback hell and how to alleviate it?
- Callbacks are a traditional way to handle asynchronous operations in JavaScript. They are functions passed as arguments to other functions and get invoked once the asynchronous operation completes. Promises, on the other hand, are objects that represent the eventual completion (or failure) of an asynchronous operation. Promises provide more structured and readable code compared to callbacks. In general promises are generally preferred over callbacks when dealing with complex asynchronous operations, error handling, and code readability.
- In callback hell, the code structure becomes deeply nested, with each callback being passed as an argument to another callback. This nesting can quickly become complex and make the code hard to follow, leading to issues such as code duplication, error handling problems, and difficulties in maintaining and debugging the code. To alleviate callback hell, several approaches can be used, such as using named functions, utilizing control flow libraries (e.g., async.js or Promises), or utilizing modern JavaScript features like async/await. These approaches help flatten the code structure and make it more readable and maintainable by avoiding excessive nesting of callbacks.
-
Explain the purpose of the
.catch()method in promises. How does it differ from the.then()method?- The
.catch()method in promises is used for error handling. It allows you to specify a callback function that will be invoked when a promise is rejected or encounters an error during its execution while on the other side.then()is used to handle fulfilled promises and successful outcomes.
- The
-
What is the difference between
Promise.resolve()andPromise.reject()?- Promise.resolve(): It returns a promise that is resolved with a given value. If the provided value is already a promise, it is returned as is. If the value is not a promise,
Promise.resolve()creates a new promise that is immediately resolved with the provided value. For example: - Promise.reject(): It returns a promise that is rejected with a given reason or error. The provided reason or error is treated as the rejection reason for the promise.
- For example:
- Promise.resolve(): It returns a promise that is resolved with a given value. If the provided value is already a promise, it is returned as is. If the value is not a promise,
const rejectedPromise = Promise.reject(new Error('Something went wrong'));
- In general
Promise.resolve()is often used when you want to create a promise that immediately resolves with a specific value whilePromise.reject()is used when you want to create a promise that immediately rejects with a specific error or reason.