Promisification
Promises/Async/Await: Promisification
What is Promisification in JavaScript?
View Answer:
let loadScriptPromise = function (src) {
return new Promise((resolve, reject) => {
loadScript(src, (err, script) => {
if (err) reject(err);
else resolve(script);
});
});
};
// usage:
// loadScriptPromise('path/script.js').then(...)
Why do we need Promisification?
View Answer:
What is a Promise in JavaScript?
View Answer:
How does Promisification relate to async/await?
View Answer:
Now let's consider a simple callback-based function that we will then promisify and use with async/await
.
// Original callback-based function
function getDataCallback(id, callback) {
setTimeout(() => {
if (id > 0) {
callback(null, { id: id, name: 'Name'+id });
} else {
callback('Invalid ID');
}
}, 2000);
}
// Promisification
function getDataPromise(id) {
return new Promise((resolve, reject) => {
getDataCallback(id, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
// Using async/await
async function printData(id) {
try {
const data = await getDataPromise(id);
console.log(data);
} catch (err) {
console.error(err);
}
}
printData(1); // Output after 2 sec: { id: 1, name: 'Name1' }
In this example, the function getDataCallback
uses the callback pattern. We've created getDataPromise
to promisify getDataCallback
. Then, we use the async/await syntax in printData
to consume getDataPromise
, which allows for easier-to-read, synchronous-style code.
What is the purpose of the resolve and reject functions in a Promise constructor?
View Answer:
How does error propagation work in Promisification?
View Answer:
// Promisified function
function riskyOperation() {
return new Promise((resolve, reject) => {
let operationSuccessful = Math.random() > 0.5; // simulate a risky operation
if (operationSuccessful) {
resolve('Operation succeeded!');
} else {
reject(new Error('Operation failed!'));
}
});
}
// Using the function with error handling
riskyOperation()
.then(result => {
console.log(result); // this will be called if operation is successful
})
.catch(error => {
console.error('Error occurred:', error.message); // this will be called if operation fails
});
In this example, if operationSuccessful
is false
, we call reject
with a new Error
object. This error can then be caught by attaching a .catch
block to the Promise. If an error is thrown in the function passed to new Promise
(even outside the reject
call), it will also be propagated as a rejection.
What is a common pattern for Promisifying a function with a single callback?
View Answer:
function promisifiedFunction(params) {
return new Promise((resolve, reject) => {
originalFunction(params, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
This promisified function can now be used with .then()
, .catch()
, or async/await
.
How does Promise.all() help in Promisification?
View Answer:
// Assume these are promisified functions that return promises
function fetchData1() {
return new Promise(resolve => {
setTimeout(() => resolve('Data 1'), 1000);
});
}
function fetchData2() {
return new Promise(resolve => {
setTimeout(() => resolve('Data 2'), 2000);
});
}
function fetchData3() {
return new Promise(resolve => {
setTimeout(() => resolve('Data 3'), 1500);
});
}
// Use Promise.all() to wait for all of them
Promise.all([fetchData1(), fetchData2(), fetchData3()])
.then(values => {
console.log(values); // ["Data 1", "Data 2", "Data 3"]
})
.catch(error => {
console.error(error);
});
In this example, Promise.all()
is used to wait for all fetchData
promises to resolve. The results are then logged in the order the promises were in the array, not the order in which they resolved. If any promise is rejected, Promise.all()
immediately rejects with the reason of the first promise that rejected, discarding all the other promises whether or not they have completed.
What is the role of Promise.prototype.finally() in Promisification?
View Answer:
// Assume this is a promisified function
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5; // random success or failure
if (success) {
resolve('Data fetched');
} else {
reject(new Error('Fetch failed'));
}
}, 2000);
});
}
// Use the fetchData promise
fetchData()
.then(data => {
console.log(data); // this will run if the promise is resolved
})
.catch(error => {
console.error(error.message); // this will run if the promise is rejected
})
.finally(() => {
console.log('Operation attempted.'); // this will always run
});
In this example, regardless of whether fetchData
is fulfilled or rejected, the finally
block will execute, logging 'Operation attempted.' This can be very useful for cleanup tasks like closing database connections, stopping loading spinners, etc.
Can you Promisify functions with multiple callbacks?
View Answer:
// Original function
function originalFunc(success1, success2, error) {
setTimeout(() => {
if (Math.random() > 0.2) { // 80% chance of success
success1('Success 1 result');
success2('Success 2 result');
} else {
error('Error result');
}
}, 1000);
}
// Promisified function
function promisifiedFunc() {
return new Promise((resolve, reject) => {
originalFunc(
result1 => resolve({result1}),
result2 => resolve({result2}),
error => reject(error)
);
});
}
// Usage
promisifiedFunc()
.then(result => console.log(result))
.catch(error => console.error(error));
This example wraps originalFunc
in a promise. If success1
or success2
are called, the promise is resolved with the respective result. If error
is called, the promise is rejected with the error. Note that once the promise is either resolved or rejected, subsequent calls to resolve
or reject
will have no effect, which is why this approach only works if each callback is expected to be called once.
If your function calls a callback multiple times, promises may not be the best solution, as they can only be resolved or rejected once. For such situations, EventEmitters or Observables (like those from RxJS) might be a better fit.
How does Promisification improve error handling?
View Answer:
Consider this callback-based function:
// Original callback-based function
function getDataCallback(id, callback) {
setTimeout(() => {
if (id > 0) {
callback(null, { id: id, name: 'Name'+id });
} else {
callback('Invalid ID');
}
}, 2000);
}
// Usage
getDataCallback(0, (err, data) => {
if (err) {
console.error(err);
} else {
console.log(data);
}
});
Now, let's promisify it and handle errors:
// Promisification
function getDataPromise(id) {
return new Promise((resolve, reject) => {
getDataCallback(id, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
// Usage with .catch()
getDataPromise(0)
.then(data => console.log(data))
.catch(err => console.error(err));
// Usage with async/await
async function printData(id) {
try {
const data = await getDataPromise(id);
console.log(data);
} catch (err) {
console.error(err);
}
}
printData(0);
In both usages of getDataPromise
, we have a clear, linear flow of error handling that's easy to read and manage. In the .catch()
example, any error in the Promise chain is handled in the .catch()
block. With async/await, we can use try-catch
just like with synchronous code.
How do you handle timeouts in Promisified functions?
View Answer:
// Utility function for creating a promise that rejects after a timeout
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error(`Operation timed out after ${ms} ms`));
}, ms);
});
}
// Assume this is a promisified function
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched");
}, 5000); // This operation takes 5 seconds to complete
});
}
// Use Promise.race to implement a timeout
Promise.race([fetchData(), timeout(3000)])
.then(data => {
console.log(data); // This will run if fetchData completes before the timeout
})
.catch(error => {
console.error(error.message); // This will run if fetchData does not complete before the timeout
});
In this example, Promise.race()
is used to start fetchData()
and timeout(3000)
in parallel. Whichever promise settles first will win the race, and its result (or error) will be passed to the following .then()
(or .catch()
). If fetchData()
takes longer than 3000 milliseconds, the timeout
promise will reject, causing the error to be logged.