C++11 header-only library based on Promises/A+
The library is designed to create and run a chain of functions or class methods. The chain runs asynchronously and returns std::future. The library supports the methods resolve, reject, then, all, all_settled, any, race, fail and finally
To create an async::promise object use the static function async::make_promise, which takes as an argument a function with optional arguments
auto promise = async::make_promise([] (int a, int b) { return a + b; }, 2, 2);To start the execution of a chain of function calls use the run method which returns std::future
auto future = async::make_promise([] { return 2; }).run();
std::cout << future.get() << std::endl; // prints 2To add the next function to a chain use the then method which takes as an argument a function with an argument of the same type as the return value of the previous function in the chain
auto future = async::make_promise([] { return 2; })
.then([] (int x) { return x * 2; })
.run();
std::cout << future.get() << std::endl; // prints 4If the then method does not need to process the value returned by the previous function, then it is allowed to pass a function that does not take an argument
auto future = async::make_promise([] { return 2; })
.then([] { return 4; })
.run();
std::cout << future.get() << std::endl; // prints 4The all method accepts an iterable of functions. Each function asynchronously processes the return value of the previous function. When all functions have completed, the method will return an iterable with the results of functions in the same order as the functions in the incoming iterable. If an error occurs while executing functions it will be thrown
std::vector<int(*)(int)> funcs
{
[] (int x) { return x * 2; },
[] (int x) { return x * 4; },
};
auto future = async::make_promise([] { return 2; })
.all(funcs)
.run();
auto results = future.get();
for (const auto& result : results)
std::cout << result << std::endl;It is also possible to create a promise object using the static function async::make_promise_all
std::vector<int(*)(int)> funcs
{
[] (int x) { return x * 2; },
[] (int x) { return x * 4; },
};
auto future = async::make_promise_all(funcs, 2)
.run();
auto results = future.get();
for (const auto& result : results)
std::cout << result << std::endl;The all_settled method accepts an iterable of functions. Each function asynchronously processes the return value of the previous function. When all functions have completed, the method will return an iterable with a special settled object that contains either a result or an error of functions in the same order as the functions in the incoming iterable
std::vector<int(*)(int)> funcs
{
[] (int x) { return x * 2; },
[] (int x) -> int { throw std::runtime_error{"I'm an error'"}; },
};
auto future = async::make_promise([] { return 2; })
.all_settled(funcs)
.run();
auto results = future.get();
for (const auto& result : results)
{
switch (result.type)
{
case async::settle_type::resolved:
std::cout << "resolved: " << result.result << std::endl;
break;
case async::settle_type::rejected:
try
{
std::rethrow_exception(result.error);
}
catch (const std::exception& e)
{
std::cout << "rejected: " << e.what() << std::endl;
}
break;
}
}It is also possible to create a promise object using the static function async::make_promise_all_settled
std::vector<int(*)(int)> funcs
{
[] (int x) { return x * 2; },
[] (int x) -> int { throw std::runtime_error{"I'm an error'"}; },
};
auto future = async::make_promise_all_settled(funcs, 2)
.run();
auto results = future.get();
for (const auto& result : results)
{
switch (result.type)
{
case async::settle_type::resolved:
std::cout << "resolved: " << result.result << std::endl;
break;
case async::settle_type::rejected:
try
{
std::rethrow_exception(result.error);
}
catch (const std::exception& e)
{
std::cout << "rejected: " << e.what() << std::endl;
}
break;
}
}The any method accepts an iterable of functions. Each function asynchronously processes the return value of the previous function. The result of the method will be the result of the first function that completes successfully. If all functions fail, an async::aggregate_error exception will be thrown
std::vector<int(*)(int)> funcs
{
[] (int x) -> int { throw std::runtime_error{"I'm an error'"}; },
[] (int x) { return x * 2; },
};
auto future = async::make_promise([] { return 2; })
.any(funcs)
.run();
std::cout << future.get() << std::endl; // prints 4It is also possible to create a promise object using the static function async::make_promise_any
std::vector<int(*)(int)> funcs
{
[] (int x) -> int { throw std::runtime_error{"I'm an error'"}; },
[] (int x) { return x * 2; },
};
auto future = async::make_promise_any(funcs, 2)
.run();
std::cout << future.get() << std::endl; // prints 4The race method accepts an iterable of functions. Each function asynchronously processes the return value of the previous function. The result of the method will be the result or error of the first completed function
std::vector<int(*)(int)> funcs
{
[] (int x) -> int { throw std::runtime_error{"I'm an error'"}; },
[] (int x) { return x * 2; },
};
auto future = async::make_promise([] { return 2; })
.race(funcs)
.run();
try
{
std::cout << future.get() << std::endl;
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl;
}It is also possible to create a promise object using the static function async::make_promise_race
std::vector<int(*)(int)> funcs
{
[] (int x) -> int { throw std::runtime_error{"I'm an error'"}; },
[] (int x) { return x * 2; },
};
auto future = async::make_promise_race(funcs, 2)
.run();
try
{
std::cout << future.get() << std::endl;
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl;
}In the all, all_settled, any and race methods it is allowed to use iterable with functions without an argument if it is not necessary to process the value returned by the previous function
std::vector<int(*)()> funcs
{
[] () { return 1; },
[] () { return 2; },
};
auto future = async::make_promise([] { return 2; })
.all(funcs)
.run();
auto results = future.get();
for (const auto& result : results)
std::cout << result << std::endl;Catching exceptions thrown in previously called functions is done by adding a fail method to the chain, which takes as an argument a function with an argument of type std::exception_ptr and returns a value of the same type as the previously called function. If the previous functions were executed without errors, then the method will return the result of the previous function
static int error_handler(const std::exception_ptr& e)
{
try
{
std::rethrow_exception(e);
}
catch(const std::exception& e)
{
return -1;
}
catch(...)
{
return -2;
}
}
auto future = async::make_promise([] { throw std::runtime_error{"I'm an error!"}; })
.fail(error_handler)
.run();
std::cout << future.get() << std::endl; // prints -1If the fail method does not need to handle a previously thrown exception, it is allowed to pass a function without an argument to the fail method
static int error()
{
throw std::runtime_error{"I'm an error"};
}
static int error_handler()
{
return -1;
}
auto future = async::make_promise(error)
.fail(error_handler)
.run();
std::cout << future.get() << std::endl; // prints -1To add the next function to a chain that will be called on both resolved and rejected states, use the finally method
auto future1 = async::make_promise([] {})
.finally([] { return "Hello World!"; })
.run();
auto future2 = async::make_promise([] { throw std::runtime_error{"I'm an error!"}; })
.finally([] { return "Hello World!"; })
.run();
std::cout << future1.get() << std::endl; // prints "Hello World!"
std::cout << future2.get() << std::endl; // prints "Hello World!"In all the above cases, you can use overloaded functions that take a class method or an iterable of class methods and a class object
my_class obj;
std::vector<int(my_class::*)(int)> all_methods
{
&my_class::method1,
&my_class::method2,
};
auto future = async::make_promise(&my_class::method, &obj)
.then(&my_class::then_method, &obj)
.all(std::move(all_methods), &obj)
.run();To create async::promise object with resolved state, use the async::make_resolved_promise static function
auto future = async::make_resolved_promise(2)
.run();
std::cout << future.get() << std::endl; // prints 2To create async::promise object with rejected state, use the async::make_rejected_promise static function
auto future = async::make_rejected_promise<int>(std::runtime_error{"I'm an error!"})
.run();
try
{
std::cout << future.get() << std::endl;
}
catch (const std::runtime_error& e)
{
std::cout << e.what() << std::endl;
}git clone https://github.com/IvanPinezhaninov/async_promise.git
cd async_promise
mkdir build
cd build
cmake -GNinja .. -DCMAKE_BUILD_TYPE=Release -DASYNC_PROMISE_BUILD_TESTS=YES
cmake --build . -- -j4
ctestThis code is distributed under the MIT License