{"id":13730579,"url":"https://github.com/paulrademacher/asyncpp","last_synced_at":"2026-02-16T12:40:39.610Z","repository":{"id":16645519,"uuid":"19400846","full_name":"paulrademacher/asyncpp","owner":"paulrademacher","description":"C++ async operations","archived":false,"fork":false,"pushed_at":"2015-05-01T19:04:18.000Z","size":564,"stargazers_count":100,"open_issues_count":2,"forks_count":10,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-08-04T02:09:42.832Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/paulrademacher.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-05-03T10:35:28.000Z","updated_at":"2023-06-26T02:32:24.000Z","dependencies_parsed_at":"2022-08-03T06:45:26.539Z","dependency_job_id":null,"html_url":"https://github.com/paulrademacher/asyncpp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulrademacher%2Fasyncpp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulrademacher%2Fasyncpp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulrademacher%2Fasyncpp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulrademacher%2Fasyncpp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paulrademacher","download_url":"https://codeload.github.com/paulrademacher/asyncpp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224695587,"owners_count":17354432,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T02:01:16.794Z","updated_at":"2026-02-16T12:40:39.549Z","avatar_url":"https://github.com/paulrademacher.png","language":"C++","readme":"# asyncpp\n\nAsyncpp is a C++ utility library for asynchronous or functional programming using modern C++ lambdas, without getting into callback hell.  It is ideally suited for use with \u003ca href=\"http://www.boost.org/doc/libs/1_56_0/doc/html/boost_asio/overview/core/basics.html\"\u003eBoost ASIO\u003c/a\u003e.\n\nThis was inspired by the popular [async](https://github.com/caolan/async) Node.js library.\n\n### What problem does this solve?\n\nIn asynchronous programming (e.g. network programming using Boost ASIO, where the thread\ndoesn't block on network calls) you lose the ability to pass data back to a calling\nfunction via return values.  And if there are many chained asynchronous operations, or\nsome combination of serial and parallel asynchronous operations, then you quickly wind up\nwith a mess of callbacks.\n\nThis library helps by packaging several common patterns of asynchronous operations, to keep your code\nclean and reasonable.\n\nHere's a contrived example.  Imagine we have to call a blocking function three times in a row.  If\nany of the invocations returns `false`, we want to return `false` to the caller.\n\n```c++\nbool func();\n\nbool call_func_three_times() {\n    for (int i = 0; i \u003c 3; i++) {\n        if (!func()) {\n            return false;\n        }\n    }\n    return true;\n}\n```\n\nNow what if the functions are non-blocking?  Note that the following naive attempt would\nnot work:\n\n```c++\nbool func_async();\n\nbool call_func_three_times_async() {\n    for (int i = 0; i \u003c 3; i++) {\n        if (!func_async()) {  // Non-blocking.\n            return false;\n        }\n    }\n    return true;\n}\n```\n\nIt can't work because the non-blocking calls can't really return the final return value,\nsince the operations they perform complete at some point in the future.  Furthermore, the\nabove code is spawning three calls in parallel, not in series.\n\nA proper version of the asynchronous code needs callbacks, and could look like this:\n\n```c++\nusing KeepGoingCallback = std::function\u003cvoid(bool keep_going)\u003e;\nusing FinalCallback = std::function\u003cvoid(bool return_code)\u003e;\n\n// This function  spawns some asynchronous activity, and eventually\n// invokes 'callback' with the value true to keep going, or false to stop.\nvoid func_async(KeepGoingCallback callback);\n\n// Start the chain of invocations.  Eventually, 'final_callback'\n// will be invoked with true if all three functions succeded,\n// or false if there were any errors.\nvoid call_function_three_times_async(FinalCallback final_callback) {\n    func_async([final_callback](bool keep_going) {\n        // This lambda is eventually invoked by func_async() when it's\n        // done.  func_async() will pass it true or false, to trigger\n        // the next step in the chain, or stop altogether.\n        if (keep_going) {\n            func_async([final_callback](bool keep_going) {\n                // This lambda is also invoked by func_async().\n                if (keep_going) {\n                    func_async([final_callback](bool keep_going) {\n                        // This lambda too.\n                        if (keep_going) {\n                            // We're done and all three calls succeeded.\n                            final_callback(true);\n                        } else {\n                            // We're done but the third call failed.\n                            final_callback(false);\n                        }\n                    });\n                } else {\n                    // The second call failed.  Stop.\n                    final_callback(false);\n                }\n            });\n        } else {\n            // The first call failed.  Stop.\n            final_callback(false);\n        }\n    });\n}\n```\n\nThis now *works*, but we're in **callback hell**.\n\n#### Cleaned up with Asyncpp\n\nUsing **Asyncpp** library, the code becomes:\n\n```c++\nusing KeepGoingCallback = std::function\u003cvoid(bool keep_going)\u003e;\nusing FinalCallback = std::function\u003cvoid(bool return_code)\u003e;\n\nvoid func_async(KeepGoingCallback callback);\n\nvoid call_function_three_times_async(FinalCallback final_callback) {\n    async::ntimes(3, func_async, final_callback);\n}\n```\n\nWe've completely generalized the pattern of multiple serial calls to an asynchronous\nfunction.\n\nThe **Asyncpp** library also has functions for parallel calls, parallel calls\nwith a limit on the number of simultaneous outstanding calls, loops, filters, and more.\n\n#### Boost example\n\nHere's an example using the Boost ASIO network library.  Instead of writing:\n\n```c++\nresolver.async_resolve(query, [=](error_code\u0026 err, ...) {\n    // Do stuff, then:\n    asio::async_connect(socket, iter, [=](error_code\u0026 err, ...) {\n        // Do stuff, then:\n        asio::async_write(socket, request, [=](error_code\u0026 err, ...) {\n            // Do stuff, then:\n            asio::asio_read_until(socket, response, \"\\r\\n\", [=](error_code\u0026 err, ...) {\n                // Do stuff, then:\n                asio::asio_read_until(socket, response, \"\\r\\n\", [=](error_code\u0026 err, ...) {\n                    // Do stuff, then:\n                    asio::asio_read_until(socket, response, \"\\r\\n\", [=](error_code\u0026 err, ...) {\n                        // Do stuff, then:\n                        asio::async_read_until(socket, response, \"\\r\\n\\r\\n\", [=](error_code\u0026 err, ...) {\n                            // Keep nesting and nesting until your tab key breaks :-(\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n```\n\nwith **asyncpp** we can write this as a flat sequence of steps:\n\n```c++\n    using Callback = async::TaskCallback\u003cint\u003e;\n    async::TaskVector\u003cint\u003e tasks {\n        [](Callback next) {\n            resolver.async_resolve(query,\n                [=](error_code\u0026 err, ...) { next(async::OK, 0); };\n        }, [](Callback next) {\n            // Do stuff, then:\n            asio::async_connect(socket, iter,\n                [=](error_code\u0026 err, ...) { next(async::OK, 1); };\n        }, [](Callback next) {\n            // Do stuff, then:\n            asio::async_write(socket, request,\n                [=](error_code\u0026 err, ...) { next(async::OK, 2); };\n        }, [](Callback next) {\n            // Do stuff, then:\n            asio::async_read_until(socket, response, \"\\r\\n\",\n                [=](error_code\u0026 err, ...) { next(async::OK, 3); };\n        }, [](Callback next) {\n            // Do stuff, then:\n            asio::async_read_until(socket, response, \"\\r\\n\",\n                [=](error_code\u0026 err, ...) { next(async::OK, 4); };\n        }, [](Callback next) {\n            // Do stuff, then:\n            asio::async_read_until(socket, response, \"\\r\\n\",\n                [=](error_code\u0026 err, ...) { next(async::OK, 5); };\n        }, [](Callback next) {\n            // Do stuff, then:\n            asio::async_read_until(socket, response, \"\\r\\n\\r\\n\",\n                [=](error_code\u0026 err, ...) { next(async::OK, 6); };\n        }\n    };\n    async::series\u003cint\u003e(tasks);\n```\n\n### Functions\n\n\u003ca name=\"each\"\u003e\n#### each\n\u003c/a\u003e\n\nTakes an input vector and a function, and applies that function to each element in the vector\n\n\u003ca name=\"map\"\u003e\n#### map\n\u003c/a\u003e\n\nTakes an input vector and a function, and applies that function to each element in the vector.  Returns (via the final_callback) a new vector with the transformed values.\n\n\u003ca name=\"series\"\u003e\n#### series\n\u003c/a\u003e\n\nInvokes a series of tasks, and collects result values into a vector. As each task completes, it invokes a callback with an error code and a result value. If the error code is not `async::OK`, iteration stops. Once all tasks complete or there is an error, `final_callback` is called with the last error and the results vector.\n\nEach task may invoke its callback immediately, or at some point in the future.\n\n\u003ca name=\"parallel\"\u003e\n#### parallel\n\u003c/a\u003e\n\nInvokes a series of tasks, each of which produces some output value, and aggregates the results into an output vector.\n\nA task may run to completion immediately, or it may defer calling its completion callback.  The latter would be if using with an asynchronous execution framework like Boost ASIO.  If each task runs to completion immediately, then this call becomes equivalent to [`series`](#series).\n\nThere is no limit on how many calls may be outstanding at the same time.\n\n\u003ca name=\"parallelLimit\"\u003e\n#### parallelLimit\n\u003c/a\u003e\n\nSame as [`parallel`](#parallel), but allows the setting of a limit on how many tasks may be concurrently outstanding.\n\n\u003ca name=\"filter\"\u003e\n#### filter\n\u003c/a\u003e\n\nTakes a vector of input data, and passes each element through a test function that returns **true** or **false**.  If **true**, that element is added to an output vector.\n\n\n\u003ca name=\"reject\"\u003e\n#### reject\n\u003c/a\u003e\n\nSimilar to [`filter`](#filter), except each element is added to the output vector if the test function returns **false**.\n\n\n\u003ca name=\"whilst\"\u003e\n#### whilst\n\u003c/a\u003e\n\nTakes two functions, `test` and `func`.  It repeatedly calls `test`, and if that returns **true**, then calls `func`.  It `test` ever returns **false** or `func` passes a non-OK (non-zero) error code to its callback, then the `whilst` function stops.\n\n\u003ca name=\"doWhilst\"\u003e\n#### doWhilst\n\u003c/a\u003e\n\nSimilar to [`whilst`](#whilst), except if follows `do..while` control flow.  That is, it calls `func` first, then `test`.  Otherwise, the same rules rules.\n\n\u003ca name=\"until\"\u003e\n#### until\n\u003c/a\u003e\n\nSimilar to [`whilst`](#whilst), except that instead of stopping when `test` returns **false**, it stops when `test` returns **true**.\n\n\u003ca name=\"doUntil\"\u003e\n#### doUntil\n\u003c/a\u003e\n\nSimilar to [`doWhilst`](#doWhilst), except that instead of stopping when `test` returns **false**, it stops when `test` returns **true**.\n\n\u003ca name=\"forever\"\u003e\n#### forever\n\u003c/a\u003e\n\nExecutes a function repetedly, until it passes a non-OK (non-zero) error code to its callback.  This is equivalent to [`whilst`](#whilst) with a `test` function that always returns **true**.\n\n\u003ca name=\"ntimes\"\u003e\n#### ntimes\n\u003c/a\u003e\n\nExecutes a function a given number of times, or until it passes a non-OK (non-zero) error code to its callback.\n\n### Summary\n\nFunction | Concurrency | Executes vec of functions | Applies single function to data vec | Returns vec of results | Output vec same size as input\n------------------------------- | :---:       | :-: | :-: | :-: | :-:\n[each](#each)                   | limit = _n_ | no  | yes | no  | n/a\n[map](#map)                     | limit = _n_ | no  | yes | yes | yes\n[series](#series)               | 1           | yes | no  | yes | yes\n[parallel](#parallel)           | no limit    | yes | no  | yes | yes\n[parallelLimit](#parallelLimit) | limit = _n_ | yes | no  | yes | yes\n[filter](#filter)               | limit = _n_ | no  | yes | yes | no\n[reject](#reject)               | limit = _n_ | no  | yes | yes | no\n[whilst](#whilst)               | 1           | no  | no  | no  | n/a\n[doWhilst](#doWhilst)           | 1           | no  | no  | no  | n/a\n[until](#until)                 | 1           | no  | no  | no  | n/a\n[doUntil](#doUntil)             | 1           | no  | no  | no  | n/a\n[forever](#forever)             | 1           | no  | no  | no  | n/a\n[ntimes](#ntimes)               | 1           | no  | no  | no  | n/a\n\n### Examples\n\nBuild using `scons`.  Binaries will be in `bin/` directory.\n\nExamples are in [/examples](/examples) directory.\n\nRun tests with `scons test`.\n\n### Requirements\n\n* C++11.\n* SCons (`brew install scons`)\n* Boost (`brew install boost`)\n\nTested with:\n\n```\nApple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)\nTarget: x86_64-apple-darwin13.3.0\nThread model: posix\n```\n\nThis library was developed primarily for use with Boost ASIO, but should support other\nsingle-thread asynchronous frameworks.  It has not been tested with multithreaded code.\n\n---------\n\nMade with :horse: by Paul Rademacher.\n","funding_links":[],"categories":["C++"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaulrademacher%2Fasyncpp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpaulrademacher%2Fasyncpp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaulrademacher%2Fasyncpp/lists"}