{"id":13857334,"url":"https://github.com/crowding/async","last_synced_at":"2025-10-22T05:59:36.743Z","repository":{"id":62459220,"uuid":"229923296","full_name":"crowding/async","owner":"crowding","description":"Asynchronous programming for R -- async/await and generators/yield","archived":false,"fork":false,"pushed_at":"2023-06-05T23:14:40.000Z","size":1584,"stargazers_count":60,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-10-22T05:59:34.313Z","etag":null,"topics":["async","async-await","asynchronous","asynchronous-programming","channels","coroutines","generator","generator-function","generator-functions","generators","iterator","iterators","r","rstats","rstats-package","streams","yield"],"latest_commit_sha":null,"homepage":"https://crowding.github.io/async/","language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/crowding.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2019-12-24T10:41:23.000Z","updated_at":"2025-10-06T09:29:51.000Z","dependencies_parsed_at":"2024-02-09T01:46:57.707Z","dependency_job_id":"a43a261e-22ed-4549-81f1-65faee468f66","html_url":"https://github.com/crowding/async","commit_stats":{"total_commits":127,"total_committers":1,"mean_commits":127.0,"dds":0.0,"last_synced_commit":"2586581456e3b9c6df982cbc7d381f796f900483"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/crowding/async","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crowding%2Fasync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crowding%2Fasync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crowding%2Fasync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crowding%2Fasync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/crowding","download_url":"https://codeload.github.com/crowding/async/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crowding%2Fasync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280389300,"owners_count":26322507,"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","status":"online","status_checked_at":"2025-10-22T02:00:06.515Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["async","async-await","asynchronous","asynchronous-programming","channels","coroutines","generator","generator-function","generator-functions","generators","iterator","iterators","r","rstats","rstats-package","streams","yield"],"created_at":"2024-08-05T03:01:33.663Z","updated_at":"2025-10-22T05:59:36.728Z","avatar_url":"https://github.com/crowding.png","language":"R","readme":"The `async` package: Generators, async/await, and asynchronous streams\nfor R\n================\n\n\u003c!-- badges: start --\u003e\n\n[![CRAN\nstatus](https://www.r-pkg.org/badges/version/async)](https://CRAN.R-project.org/package=async)\n[![R-CMD-check](https://github.com/crowding/async/actions/workflows/check-standard.yaml/badge.svg)](https://github.com/crowding/async/actions/workflows/check-standard.yaml)\n[![pkgdown](https://github.com/crowding/async/actions/workflows/pkgdown.yaml/badge.svg)](https://crowding.github.io/async/)\n[![codecov](https://codecov.io/gh/crowding/async/branch/main/graph/badge.svg?token=kqLgHxP1Gh)](https://app.codecov.io/gh/crowding/async)\n\u003c!-- badges: end --\u003e\n\nThis is an R package implementing *generators*, *async* blocks, and\n*streams* (collectively known as “coroutines.”)\n\n## New features in version 0.3\n\n  - Coroutines now support single step debugging. Use `debugAsync(obj,\n    TRUE)` to pause before each call at R level. You can also use\n    `debugAsync(obj, internal=TRUE)` to step through at the coroutine\n    implementation level.\n  - Coroutines are printed with a label indicating where in their code\n    they are paused.\n  - `switch` supports `goto()` to transfer to a different branch.\n  - Coroutines now support `on.exit()`.\n  - There is now syntax for generator functions: `gen(function(x, y)\n    ...)` returns a function that constructs generators.\n  - `run(...)` will execute a generator expression immediately and\n    collect the results in a list.\n  - There is now an experimental `stream()` coroutine backed by a\n    `channel` class (asynchronous iterator).\n  - The underlying implementation now includes the back half of a\n    compiler. As evidence of this, you can draw a graph of a coroutine’s\n    control structures by calling `graphAsync(gen)` (this requires you\n    have Graphviz `dot` command installed on your system.)\n\nFor more details see\n[NEWS.md](https://github.com/crowding/async/blob/main/NEWS.md).\n\n## Generators\n\n`g \u003c- gen({...})` is like a function that knows how to “pause.” The code\nin a generator runs until it hits a `yield()` call, then returns that\nvalue. The next time you call the generator it picks up where it left\noff and runs until the next `yield`.\n\nFrom the outside a generator implements the `iteror` interface. You\nextract each yielded value with `nextOr(g, or)`, and you can use\ngenerators anywhere you can use an iteror. The `iteror` class is cross\ncompatible with the\n[iterators](https://CRAN.R-project.org/package=iterators) package.\n\n### Example: Collatz sequence\n\nConsider a sequence of numbers `x[i]`, starting with an arbitrary\n`x[1]`, where each subsequent element is produced by applying the rule:\n\n  - If `x[i]` is even, then the next value will be `x[i+1] = x[i]/2`.\n  - if `x[i]` is odd, the next value will be `x[i+1] = 3*x[i]+1`.\n\nAn infinite sequence of numbers will continue form each staring point\n`x[1]`, but it is\n[conjectured](https://en.wikipedia.org/wiki/Collatz_conjecture) that all\nsequences will eventually reach the loop 1, 4, 2, 1, 4, 2, …. The\nfollowing generator produces the Collatz sequence, starting from `x`,\nand terminating when (or if?) the sequence reaches 1.\n\n``` r\nlibrary(async)\ncollatz \u003c- gen(function(x) {\n  yield(x)\n  while (x \u003e 1) {\n    if (x %% 2 == 0)\n      yield(x \u003c- x / 2L)\n    else yield(x \u003c- 3L * x + 1)\n  }\n})\n```\n\nThe call to `gen` produces a generator. You can get values one at a time\nwith `nextOr()`.\n\n``` r\nctz \u003c- collatz(12)\nnextOr(ctz, NA)\n```\n\n    ## [1] 12\n\n``` r\nnextOr(ctz, NA)\n```\n\n    ## [1] 6\n\n``` r\nnextOr(ctz, NA)\n```\n\n    ## [1] 3\n\n``` r\nnextOr(ctz, NA)\n```\n\n    ## [1] 10\n\n``` r\nnextOr(ctz, NA)\n```\n\n    ## [1] 5\n\nYou can also use any other method that applies to an iterator, like\n`as.list`.\n\n``` r\ncollatz(27L) |\u003e as.list() |\u003e as.numeric()\n```\n\n    ##   [1]   27   82   41  124   62   31   94   47  142   71  214  107  322  161  484\n    ##  [16]  242  121  364  182   91  274  137  412  206  103  310  155  466  233  700\n    ##  [31]  350  175  526  263  790  395 1186  593 1780  890  445 1336  668  334  167\n    ##  [46]  502  251  754  377 1132  566  283  850  425 1276  638  319  958  479 1438\n    ##  [61]  719 2158 1079 3238 1619 4858 2429 7288 3644 1822  911 2734 1367 4102 2051\n    ##  [76] 6154 3077 9232 4616 2308 1154  577 1732  866  433 1300  650  325  976  488\n    ##  [91]  244  122   61  184   92   46   23   70   35  106   53  160   80   40   20\n    ## [106]   10    5   16    8    4    2    1\n\n``` r\n#Try collatz(63728127L) |\u003e as.list() |\u003e as.numeric()...\n```\n\nFor more examples, see the [“Clapping Music”\nvignette.](https://crowding.github.io/async/articles/clapping.html)\n\n## Async/await\n\nLike `gen`, `async({...})` takes a block of sequential code, which runs\nuntil it reaches a call to `await(p)`. The argument `p` should be a\npromise, (as defined by the\n[promises](https://rstudio.github.io/promises/ \"promises\") package,\nwhich represents an unfinished external computation.) In turn, `async()`\nconstructs and returns a promise.\n\nAn `async` block runs until it reaches a call to `await(p)` and pauses.\nWhen the promise `p` resolves, the `async` block continues. If `p`\nrejects, that is evaluated like an error; you can use `await(p)` into a\n`tryCatch` to handle rejections. When the `async` block finishes, or\nthrows an error, its promise resolves or rejects.\n\n### Examples:\n\n`async` doesn’t handle running concurrent tasks by itself; it builds on\nexisting packages like `future` and `later`. The `later` package lets\nyou assign tasks to be done in the event loop, when R is idle.\n\nRing a bell 5 times at 10 second intervals (subject to R being idle):\n\n``` r\nasync({\n  for (i in 1:5) {\n    await(delay(10))     #delay() uses later::later()\n    cat(\"Beep\", i, \"\\n\")\n    beepr::beep(2)\n  }\n})\n```\n\n#### Shiny apps\n\n`async()` can be used in Shiny apps\\! For an example, here is a version\nof the [“Cranwhales” demo app using\nasync/await.](https://github.com/crowding/cranwhales-await).\n\n#### Web scraping\n\n`async()` allows you to naturally keep track of more than one concurrent\nprocess. The [web spider\nvignette](https://crowding.github.io/async/articles/spider.html) shows\nhow this can improve the speed of web scraping using concurrent\nconnections.\n\n#### Background processing\n\n`async` can also work with `future` objects to run computations in\nparallel. Download, parse, and summarize a dataset in background\nprocesses like this:\n\n``` r\nlibrary(future)\nlibrary(dplyr)\nplan(multiprocess(workers=2))\n\nurl \u003c- \"http://analytics.globalsuperhypermegamart.com/2020/March.csv.gz\"\ndest \u003c- \"March.csv.gz\"\n\ndataset \u003c- async({\n  if(!file.exists(dest)) {\n    await(future({\n      cat(\"Downloading\\n\")\n      download.file(url, dest)\n    }))\n  }\n  data \u003c- await(future({\n    cat(\"Parsing\\n\")\n    read.csv(dest) |\u003e\n    mutate(time = hms::trunc_hms(time, 60*60)) |\u003e\n    group_by(time) |\u003e\n    summarize(sales=sum(amount))\n  }))\n})\n\n# When the data is ready, plot it (in the main process:)\nasync({\n  await(dataset) |\u003e\n  ggplot(aes(time, n)) +\n    xlab(\"Time\") +\n    ylab(\"Sales\")\n})\n```\n\n## Streams\n\nNew in version 0.3 are asynchronous streams and channels. A channel is\nan interface for asynchronous iteration; `stream()` lets you do things\nwith channels by writing code with `await` and `yield`. Here is an\nexample of channels being used to “walk and chew gum concurrently:”\n\n``` r\nwalk \u003c- stream({\n  for (i in 1:10)\n    for (step in c(\"left\", \"right\")) {\n      yield(step)\n      await(delay(0.5))\n    }\n})\n\nchewGum \u003c- stream(for (i in 1:12) {\n  yield(\"chew\")\n  await(delay(0.8))\n})\n\nprintEach \u003c- async(function(st) {\n  for (each in st) {cat(each, \", \", sep=\"\")}\n  cat(\"\\n\")\n})\n\nall \u003c- combine(walk, chewGum) |\u003e printEach()\n```\n\n    ## left, chew, right, chew, left, right, chew, left, chew, right, left, chew, right, chew, left, right, chew, left, right, chew, left, chew, right, left, chew, right, chew, left, right, chew, left, right,\n\n## How does this work anyway?\n\nA longer article will be forthcoming, but the basic gist is the `async`\npackage transforms your given program into a state machine.\n\nA coroutine expression is first scanned for uses of `await`, `yield`,\n`for`, `break` and other control flow calls. Those calls are swapped out\nfor implementations local to the `async` package. Other R calls are\nwrapped in functions; all these functions are linked together in so that\neach function calls the next in sequence. The result is a graph of\nfunctions calling each other, each call corresponding to a step in the\nprogram.\n\nAs of `async` version 0.3 you can extract and visualize this graph with\n`graphAsync(g)`. (You will need Graphviz `dot` installed to render these\ngraphs.\n\n``` r\nctz \u003c- collatz(23)\ngraphAsync(ctz, type=\"svg\") #creates a file \"ctz.svg\"\n```\n\n![Graph of the Collatz generator.](ctz.svg)\n\nSince each step in the program’s execution corresponds to a function\ncall, when execution reaches a `yield`, the program’s state is just the\n“next function” that would have been called (that is, a\n[continuation](https://en.wikipedia.org/wiki/Continuation).) To pause\nand resume execution, a generator saves that “next function” until the\nnext time `nextOr()` is called.\n\nYou can also enable single-stepping at the graph level by calling:\n\n``` r\ndebugAsync(ctz, internal=TRUE)\n```\n","funding_links":[],"categories":["R"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrowding%2Fasync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrowding%2Fasync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrowding%2Fasync/lists"}