- Published on
BigFrontEnd Category 15 Promise Implementation Questions
- Authors
- Name
- Yinhuan Yuan
Introduction
This blog post summarizes the Promise implementation related questions found on BigFrontEnd.Dev.
- 1.Implement Promise.all()
- 2.Implement Promise.allSettled()
- 3.Implement Promise.any()
- 4.Implement Promise.race()
- 5.auto-retry Promise on rejection
- 6.create your own Promise
- 7.throttle Promises
- 8.implement Promise.prototype.finally()
- 9.Add Two Promises (2723)
- 10.Sleep (2621)
- 11.Promise Time Limit (2637)
- 12.Implement promisify()
- 13.Promise Pool (2636)
- 14.call APIs with pagination
- 15.implement async helper - sequence()
- 16.implement async helper - parallel()
- 17.implement async helper - race()
Promise.all()
1.Implement If any promise fails (i.e., rejects), the entire Promise.all()
operation fails immediately, and the returned promise is rejected with the reason of the first rejected promise.
Promise.all()
1.1.Implement 32 https://bigfrontend.dev/problem/implement-Promise-all
The Promise.all()
method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises
Could you write your own all() ? which should works the same as Promise.all()
note
Do not use Promise.all()
directly, it is not helping
Solution: Find all resolved promises or reject with the first error.
- It takes an array of promises as the input.
- It must retrun a Promise (
return new Promise((resolve, reject) => {})
) - Create a results to store the results from promise.
- Use
forEach
to iterate through each promise withthen
andcatch
. - In
then
callback, it assign the result and increment the count. If the count reach the size of promises, return the results. - In
catch
callback, reject the promise with the same error.
/**
* @param {Array<any>} promises - notice input might have non-Promises
* @return {Promise<any[]>}
*/
function all(promises) {
const n = promises.length
const convertToPromise = (promise) =>
promise instanceof Promise ? promise : new Promise((resolve) => resolve(promise))
promises = promises.map(convertToPromise)
const results = Array(n)
let numOfResults = 0
return new Promise((resolve, reject) => {
// edge cases
if (n === 0) {
resolve([])
}
promises.forEach((promise, index) => {
promise
.then((data) => {
results[index] = data
numOfResults += 1
if (numOfResults === n) {
resolve(results)
}
})
.catch(reject)
})
})
}
Promise.all()
1.2.Implement // 2721. Execute Asynchronous Functions in Parallel
type Fn<T> = () => Promise<T>
function promiseAll<T>(functions: Fn<T>[]): Promise<T[]> {
return new Promise<T[]>((resolve, reject) => {
const result: T[] = Array(functions.length);
let count = 0;
functions.forEach((func, i) => {
func().then(val => {
result[i] = val;
count += 1;
if (count === functions.length) {
resolve(result);
}
}).catch(e => {
reject(e);
});
});
});
};
const promises = [
Promise.resolve('fulfilled value 1'),
Promise.resolve('fulfilled value 2'),
new Promise(resolve => setTimeout(() => resolve('fulfilled value 3'), 1000))
];
all(promises)
.then(results => {
console.log(results);
// Output: ['fulfilled value 1', 'fulfilled value 2', 'fulfilled value 3']
})
.catch(error => {
console.error(error);
});
Promise.allSettled()
2.Implement 33 https://bigfrontend.dev/problem/implement-Promise-allSettled
Use Promise.allSettled()
when you want to wait for all promises to complete, regardless of whether they fulfill or reject, and you need to know the outcome of each individual promise.
Solution: Find the first resolved promise.
- It takes an array of promises as the input.
- It must retrun a Promise (
return new Promise((resolve, reject) => {})
) - Create an array to store the results from promise.
- Use
forEach
to iterate through each promise withthen
andcatch
. - In
then
callback, fills the results with the result. - In
catch
callback, fills the results with the error. - If the count reach the size of promises, resolve it with the results..
/**
* @param {Array<any>} promises - notice that input might contains non-promises
* @return {Promise<Array<{status: 'fulfilled', value: any} | {status: 'rejected', reason: any}>>}
*/
function allSettled(promises) {
const n = promises.length;
const convertToPromise = promise => (promise instanceof Promise) ? promise : new Promise((resolve) => resolve(promise));
promises = promises.map(convertToPromise);
const results = Array(n);
let numOfResults = 0;
return new Promise((resolve, reject) => {
const checkCompleted = () => {
numOfResults += 1;
if (numOfResults === n) {
resolve(results);
}
};
// edge cases
if (n === 0) {
resolve([]);
}
promises.forEach((promise, index) => {
promise
.then((data) => {
results[index] = { status: "fulfilled", value: data };
checkCompleted();
})
.catch(error => {
results[index] = { status: "rejected", reason: error };
checkCompleted();
});
});
});
}
Promise.any()
3.Implement 34 https://bigfrontend.dev/problem/implement-Promise-any
Waits for any of the promises in the iterable to be fulfilled. Returns a single Promise that resolves with the value of the first fulfilled promise, or rejects with an AggregateError
if all input promises are rejected.
Solution: Find the first resolved promise.
- It takes an array of promises as the input.
- It must retrun a Promise (
return new Promise((resolve, reject) => {})
) - Create an array to store the errors from promise.
- Use
forEach
to iterate through each promise withthen
andcatch
. - In
then
callback, it tests whether the promise is fullfilled. If it is not, resolve it. - In
catch
callback, it assign the error and increment the error count. If the count reach the size of promises, reject with the errors.
/**
* @param {Array<Promise>} promises
* @return {Promise}
*/
function any(promises) {
const convertToPromise = (promise) =>
promise instanceof Promise ? promise : new Promise((resolve) => resolve(promise))
promises = promises.map(convertToPromise)
const n = promises.length
const errors = Array(n)
let numOfErrors = 0
return new Promise((resolve, reject) => {
if (n === 0) {
resolve()
}
promises.forEach((promise, index) =>
promise.then(resolve).catch((error) => {
errors[index] = error
numOfErrors += 1
if (numOfErrors === n) {
reject(new AggregateError('No Promise in Promise.any was resolved', errors))
}
})
)
})
}
Promise.race()
4.Implement 35 https://bigfrontend.dev/problem/implement-Promise-race
Waits for the first promise in the iterable to be settled (fulfilled or rejected).
Solution: Find the first resolved/rejected promise.
- It takes an array of promises as the input.
- It must retrun a Promise (
return new Promise((resolve, reject) => {})
) - Use
forEach
to iterate through each promise withthen
andcatch
. - In
then
callback, If it is fullfilled, resolve it. - In
catch
callback, if it is rejected, reject it.
/**
* @param {Array<Promise>} promises
* @return {Promise}
*/
function race(promises) {
const convertToPromise = (promise) =>
promise instanceof Promise ? promise : new Promise((resolve) => resolve(promise))
promises = promises.map(convertToPromise)
return new Promise((resolve, reject) => {
promises.forEach((promise) => promise.then(resolve, reject))
})
}
5.auto-retry Promise on rejection
64.https://bigfrontend.dev/problem/retry-promise-on-rejection
For a web application, fetching API data is a common task.
But the API calls might fail because of Network problems. Usually we could show a screen for Network Error and ask users to retry.
One approach to handle this is auto retry when network error occurs.
You are asked to create a fetchWithAutoRetry(fetcher, count)
, which automatically fetch again when error happens, until the maximum count is met.
For the problem here, there is no need to detect network error, you can just retry on all promise rejections.
Solution:
The call of a async function returns a promise.
/**
* @param {() => Promise<any>} fetcher
* @param {number} maximumRetryCount
* @return {Promise<any>}
*/
function fetchWithAutoRetry(fetcher, maximumRetryCount) {
const helper = async () => {
try {
const result = await fetcher()
return result
} catch (error) {
if (maximumRetryCount === 0) {
return Promise.reject('error')
} else {
return await fetchWithAutoRetry(fetcher, maximumRetryCount - 1)
}
}
}
return helper()
}
6.create your own Promise
67.https://bigfrontend.dev/problem/create-your-own-Promise
Promise is widely used nowadays, hard to think how we handled Callback Hell in the old times.
Can you implement a MyPromise
Class by yourself?
At least it should match following requirements
- new promise:
new MyPromise((resolve, reject) => {})
- chaining :
MyPromise.prototype.then()
then handlers should be called asynchronously - rejection handler:
MyPromise.prototype.catch()
- static methods:
MyPromise.resolve()
,MyPromise.reject()
. This is a challenging problem. Recommend you read about Promise thoroughly first.
Solution:
Here's a breakdown of the implementation:
Constructor: Initializes the promise with pending state and sets up resolve and reject functions.
then method: Implements chaining and ensures handlers are called asynchronously using
queueMicrotask
.catch method: Implements error handling by calling
then
with null as the success handler.resolvePromise method: Handles the resolution of promises, including thenable objects and potential chaining cycles.
Static resolve and reject methods: Implement the static methods for creating pre-resolved or pre-rejected promises.
Key points about this implementation:
- It uses a state machine (PENDING, FULFILLED, REJECTED) to manage the promise's state.
- The
then
method returns a new promise, allowing for chaining. - Handlers are executed asynchronously using
queueMicrotask
to match the behavior of native Promises. - It handles thenables and potential chaining issues in the
resolvePromise
method. - The implementation follows the Promise/A+ specification in many aspects, though a full spec-compliant implementation would require more extensive error checking and edge case handling.
This implementation covers the basic functionality of Promises and demonstrates the core concepts. However, it doesn't include more advanced features like Promise.all
, Promise.race
, or cancellation, which could be added as extensions to this base implementation.
// Define the possible states of a promise
const STATE = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
};
class MyPromise {
constructor(executor) {
this.state = STATE.PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === STATE.PENDING) {
this.state = STATE.FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(callback => callback());
}
};
const reject = (reason) => {
if (this.state === STATE.PENDING) {
this.state = STATE.REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(callback => callback());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
if (this.state === STATE.FULFILLED) {
fulfilledMicrotask();
} else if (this.state === STATE.REJECTED) {
rejectedMicrotask();
} else if (this.state === STATE.PENDING) {
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Chaining cycle detected for promise'));
return;
}
let called = false;
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
static resolve(value) {
return new MyPromise((resolve) => {
resolve(value);
});
}
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
}
7.throttle Promises
92.https://bigfrontend.dev/problem/throttle-Promises
Say you need to fetch some data through 100 APIs, and as soon as possible.
If you use Promise.all()
, 100 requests go to your server at the same time, which is a burden to low spec servers.
Can you throttle your API calls so that always maximum 5 API calls at the same time?
You are asked to create a general throttlePromises() which takes an array of functions returning promises, and a number indicating the maximum concurrent pending promises.
throttleAsync(callApis, 5)
.then((data) => {
// the data is the same as `Promise.all`
})
.catch((err) => {
// any error occurs in the callApis would be relayed here
})
By running above code, at any time, no more than 5 APIs are requested, so low spec servers are saved.
Solution:
/**
* @param {() => Promise<any>} func
* @param {number} max
* @return {Promise}
*/
function throttlePromises(tasks, maxConcurrent) {
return new Promise((resolve, reject) => {
const results = []
let runningCount = 0
let index = 0
function runNext() {
// If all tasks are completed, resolve the final results
if (index === tasks.length && runningCount === 0) {
resolve(results)
return
}
// If the number of currently running tasks is less than maxConcurrent
while (runningCount < maxConcurrent && index < tasks.length) {
const currentIndex = index++
const task = tasks[currentIndex]
runningCount++
task()
.then((result) => {
results[currentIndex] = result
})
.catch((err) => {
reject(err)
})
.finally(() => {
runningCount--
runNext() // Run the next task when the current one completes
})
}
}
runNext()
})
}
8.implement Promise.prototype.finally()
123.https://bigfrontend.dev/problem/implement-Promise-prototype-finally
Promise.prototype.finally() could be used to run a callback when a promise is settled(either fulfilled or rejected).
Notice that the callback passed finally() doesn't receive any argument, meaning it doesn't modify the value in the promise chain (care for rejection).
Solution: Find the first resolved/rejected promise.
- It takes a promise and a onFinally function as the input.
- It must retrun a Promise (
return new Promise((resolve, reject) => {})
) - Attach the
then
andcatch
to the promise. - In
then
async callback, call onFinally and resolve it if it succeedsand reject it if it fails. - In
catch
async callback, call onFinally and reject it.
/**
* @param {Promise<any>} promise
* @param {() => void} onFinally
* @returns {Promise<any>}
*/
function myFinally(promise, onFinally) {
return new Promise((resolve, reject) => {
promise
.then(async (result) => {
// Add async to support the case that onFinally() return a promise.
try {
await onFinally()
resolve(result)
} catch (err) {
reject(err)
}
})
.catch(async (error) => {
try {
await onFinally()
reject(error)
} catch (err) {
reject(err)
}
})
})
}
9.Add Two Promises (2723)
type P = Promise<number>
async function addTwoPromises(promise1: P, promise2: P): P {
try {
const [val1, val2] = await Promise.all([promise1, promise2]);
return val1 + val2;
} catch {
throw new Error("promise error");
}
};
10.Sleep (2621)
async function sleep(millis: number): Promise<void> {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), millis);
});
}
11.Promise Time Limit (2637)
type Fn = (...params: any[]) => Promise<any>;
function timeLimit(fn: Fn, t: number): Fn {
return async function(...args) {
return Promise.race([
fn(...args),
new Promise<any>((resolve, reject) => {
setTimeout(() => {
reject("Time Limit Exceeded");
}, t);
}),
]);
}
};
promisify()
12.Implement 159.https://bigfrontend.dev/problem/promisify
Let's take a look at following error-first callback.
const callback = (error, data) => {
if (error) {
// handle the error
} else {
// handle the data
}
}
Now think about async functions that takes above error-first callback as last argument.
const func = (arg1, arg2, callback) => {
// some async logic
if (hasError) {
callback(someError)
} else {
callback(null, someData)
}
}
You see what needs to be done now. Please implement promisify() to make the code better.
const promisedFunc = promisify(func)
promisedFunc()
.then((data) => {
// handles data
})
.catch((error) => {
// handles error
})
Solution:
function promisify(func) {
return function (...args) {
return new Promise((resolve, reject) => {
// Invoke the original function with the provided arguments
func(...args, (err, result) => {
if (err) {
reject(err); // If an error occurs, reject the Promise with the error
} else {
resolve(result); // If successful, resolve the Promise with the result
}
});
});
};
}
const fs = require('fs');
// Example: promisify the fs.readFile function
const readFilePromise = promisify(fs.readFile);
readFilePromise('example.txt', 'utf8')
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
13.Promise Pool (2636)
// https://dev.to/endeavourmonk/promise-pool-javascript-leetcode-2636-598k
type F = () => Promise<any>;
function promisePool(functions: F[], n: number): Promise<any> {
return new Promise<any>((resolve) => {
let inProgress = 0, index = 0;
const results: any[] = [];
function helper() {
// base case
if (index >= functions.length) {
if (inProgress === 0) resolve(results);
return;
}
while (inProgress < n && index < functions.length) {
inProgress++;
functions[index]().then((val) => {
results[index] = val;
inProgress--;
helper();
});
index += 1;
}
}
helper();
});
}
14.call APIs with pagination
56.https://bigfrontend.dev/problem/call-APIs-with-pagination
Have you ever met some APIs with pagination, and needed to recursively fetch them based on response of previous request ?
Suppose we have a /list
API, which returns an array items
.
// fetchList is provided for you
const fetchList = (since?: number) =>
Promise<{items: Array<{id: number}>}>
- for initial request, we just fetch fetchList. and get the last item id from response.
- for next page, we need to call fetchList(lastItemId).
- repeat above process.
The /list
API only gives us 5 items at a time, with server-side filtering, it might be less than 5. But if none returned, it means nothing to fetch any more and we should stop.
You are asked to create a function that could return arbitrary amount of items.
const fetchListWithAmount = (amount: number = 5) { }
note
You can achieve this by regular loop, even fancier solutions with async iterators or async generators. You should try them all.
Solution:
14.1
// fetchList is provided for you
// const fetchList = (since?: number) =>
// Promise<{items: Array<{id: number}>}>
// you can change this to generator function if you want
const fetchListWithAmount = async (amount = 5) => {
const allItems = []
let lastItemId = undefined
while (allItems.length < amount) {
const response = await fetchList(lastItemId)
const items = response.items
if (items.length === 0) {
break
}
allItems.push(...items)
lastItemId = items[items.length - 1].id
if (allItems.length >= amount) {
break
}
}
return allItems.slice(0, amount)
}
14.2 Solution with async iterators
// fetchList is provided for you
// const fetchList = (since?: number) =>
// Promise<{items: Array<{id: number}>}>
// you can change this to generator function if you want
const fetchListWithAmount = async (amount = 5) => {
const items = []
for await (const newItems of fetchPaginated(amount)) {
items.push(...newItems)
}
return items.slice(0, amount)
}
function fetchPaginated(amount) {
const iterator = {
amount,
itemsAmount: 0,
lastItemId: null,
async next() {
if (this.itemsAmount > this.amount) {
return { done: true }
}
const response = this.lastItemId ? await fetchList(this.lastItemId) : await fetchList()
if (!response || !response.items.length) {
return { done: true }
}
const result = response.items
this.itemsAmount += result.length
this.lastItemId = result[result.length - 1].id
return {
done: false,
value: result,
}
},
}
return {
[Symbol.asyncIterator]() {
return iterator
},
}
}
14.3 Solution with async generators
// fetchList is provided for you
// const fetchList = (since?: number) =>
// Promise<{items: Array<{id: number}>}>
// you can change this to generator function if you want
const fetchListAsyncGenerator = async function* () {
let lastItemId = undefined
while (true) {
const response = await fetchList(lastItemId)
const items = response.items
if (items.length === 0) {
break
}
yield* items
lastItemId = items[items.length - 1].id
}
}
const fetchListWithAmount = async (amount = 5) => {
const allItems = []
const generator = fetchListAsyncGenerator()
for await (const item of generator) {
allItems.push(item)
if (allItems.length >= amount) {
break
}
}
return allItems.slice(0, amount)
}
// Usage
fetchListWithAmount(12).then(console.log)
sequence()
15.implement async helper - 29.https://bigfrontend.dev/problem/implement-async-helper-sequence
This problem is similar to 11. what is Composition? create a pipe().
You are asked to implement an async function helper, sequence() which chains up async functions, like what pipe() does.
All async functions have following interface
type Callback = (error: Error, data: any) => void
type AsyncFunc = (callback: Callback, data: any) => void
Your sequence()
should accept AsyncFunc array, and chain them up by passing new data to the next AsyncFunc through data in Callback.
Suppose we have an async func which just multiple a number by 2
const asyncTimes2 = (callback, num) => {
setTimeout(() => callback(null, num * 2), 100)
}
Your sequence()
should be able to accomplish this
const asyncTimes4 = sequence([asyncTimes2, asyncTimes2])
asyncTimes4((error, data) => {
console.log(data) // 4
}, 1)
Once an error occurs, it should trigger the last callback without triggering the uncalled functions.
Follow up
Can you solve it with and without Promise?
Solution:
sequence
takes an array of async function as input and returns a function which takes a callback and initial data as input and returns void.
We can convert the async functions to promises and use async and await to achieve the goal.
const promisify = (asyncFunc, currentData) =>
new Promise((resolve, reject) => {
asyncFunc((error, result) => {
if (error) {
return reject(error)
}
resolve(result)
}, currentData)
})
/**
* @param {AsyncFunc[]} funcs
* @return {(finalCallback: Callback, initialData) => void}
*/
function sequence(asyncFuncs) {
return async (finalCallback, initialData) => {
let currentData = initialData
try {
for (const asyncFunc of asyncFuncs) {
currentData = await promisify(asyncFunc, currentData)
}
finalCallback(undefined, currentData)
} catch (error) {
finalCallback(error, undefined)
}
}
}
parallel()
16.implement async helper - 30.https://bigfrontend.dev/problem/implement-async-helper-parallel
This problem is related to 29. implement async helper - sequence().
You are asked to implement an async function helper, parallel() which works like Promise.all(). Different from sequence(), the async function doesn't wait for each other, rather they are all triggered together.
All async functions have following interface
type Callback = (error: Error, data: any) => void
type AsyncFunc = (callback: Callback, data: any) => void
Your parallel()
should accept AsyncFunc array, and return a new function which triggers its own callback when all async functions are done or an error occurs.
Suppose we have an 3 async functions
const async1 = (callback) => {
callback(undefined, 1)
}
const async2 = (callback) => {
callback(undefined, 2)
}
const async3 = (callback) => {
callback(undefined, 3)
}
Your parallel()
should be able to accomplish this
const all = parallel([async1, async2, async3])
all((error, data) => {
console.log(data) // [1, 2, 3]
}, 1)
When error occurs, only first error is passed down to the last. Later errors or data are ignored.
Solution:
We can convert the async functions to promoises and use Promise.all
to achieve the goal.
/**
* @param {AsyncFunc[]} funcs
* @return {(callback: Callback) => void}
*/
function parallel(funcs) {
return (finalCallback, initialData) => {
const promises = funcs.map((func) => promisify(func, initialData))
Promise.all(promises)
.then((result) => finalCallback(undefined, result))
.catch((error) => finalCallback(error, undefined))
}
}
race()
17.implement async helper - 31.https://bigfrontend.dev/problem/implement-async-helper-race
This problem is related to 30. implement async helper - parallel().
You are asked to implement an async function helper, race()
which works like Promise.race()
. Different from parallel() that waits for all functions to finish, race() will finish when any function is done or run into error.
All async functions have following interface
type Callback = (error: Error, data: any) => void
type AsyncFunc = (callback: Callback, data: any) => void
Your race()
should accept AsyncFunc array, and return a new function which triggers its own callback when any async function is done or an error occurs.
Suppose we have an 3 async functions
const async1 = (callback) => {
setTimeout(() => callback(undefined, 1), 300)
}
const async2 = (callback) => {
setTimeout(() => callback(undefined, 2), 100)
}
const async3 = (callback) => {
setTimeout(() => callback(undefined, 3), 200)
}
Your race()
should be able to accomplish this
const first = race([async1, async2, async3])
first((error, data) => {
console.log(data) // 2, since 2 is the first to be given
}, 1)
When error occurs, only first error is passed down to the last. Later errors or data are ignored.
Solution:
We can convert the async functions to promoises and use Promise.race
to achieve the goal.
/**
* @param {AsyncFunc[]} funcs
* @return {(callback: Callback) => void}
*/
function race(funcs) {
return (finalCallback, initialData) => {
const promises = funcs.map((func) => promisify(func, initialData))
Promise.race(promises)
.then((result) => finalCallback(undefined, result))
.catch((error) => finalCallback(error, undefined))
}
}