{"id":16326457,"url":"https://github.com/blueneogeo/xtend-async","last_synced_at":"2026-02-07T11:32:47.850Z","repository":{"id":15403718,"uuid":"18135698","full_name":"blueneogeo/xtend-async","owner":"blueneogeo","description":"Promises and Java8 / RXJava like streaming for Xtend","archived":false,"fork":false,"pushed_at":"2018-02-16T10:16:52.000Z","size":2835,"stargazers_count":15,"open_issues_count":0,"forks_count":1,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-11-30T08:25:30.722Z","etag":null,"topics":["asynchronous","events","fibers","promise","stream","xtend"],"latest_commit_sha":null,"homepage":null,"language":"Xtend","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/blueneogeo.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}},"created_at":"2014-03-26T11:22:23.000Z","updated_at":"2023-09-08T16:46:32.000Z","dependencies_parsed_at":"2022-08-25T10:40:21.310Z","dependency_job_id":null,"html_url":"https://github.com/blueneogeo/xtend-async","commit_stats":null,"previous_names":[],"tags_count":64,"template":false,"template_full_name":null,"purl":"pkg:github/blueneogeo/xtend-async","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blueneogeo%2Fxtend-async","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blueneogeo%2Fxtend-async/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blueneogeo%2Fxtend-async/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blueneogeo%2Fxtend-async/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/blueneogeo","download_url":"https://codeload.github.com/blueneogeo/xtend-async/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blueneogeo%2Fxtend-async/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29193598,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T07:37:03.739Z","status":"ssl_error","status_checked_at":"2026-02-07T07:37:03.029Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["asynchronous","events","fibers","promise","stream","xtend"],"created_at":"2024-10-10T23:08:26.197Z","updated_at":"2026-02-07T11:32:47.835Z","avatar_url":"https://github.com/blueneogeo.png","language":"Xtend","funding_links":[],"categories":[],"sub_categories":[],"readme":"# XTEND-ASYNC\n\nXtend-async provides asynchronous streams, promises and functions to Xtend. It can be used for any Java-based project, but is specifically built to work well with the Xtend language. It has no runtime dependencies apart from the small Xtend Java library and Google Guava.\n\nMain features:\n\n- easy to use, simple syntax\n- asynchronous, non-blocking and thread-safe \n- integrates the concepts of streaming and promising\n\nSome features are:\n\n- lightweight, with no dependencies besides Xtend and Xtend-tools\n- fast, and threadsafe when you tell it to\n- streams and promises are integrated and work with each other and use nearly the exact same syntax\n- clear source code, the base Stream and Promise classes are as simple as possible. All features are added with Xtend extensions. This lets you add your own operators easily, as well as easily debug code.\n- streams are controlled and support back pressure, meaning that you can indicate when a listener is ready to process a next item from a stream\n- streams and promises in xtend-stream encapsulate errors thrown in your handlers and propagate them so you can listen for then\n- streams and promises keep a reference to the input, letting you for example respond to a request without leaving the stream or promise chain.\n\n# XTEND-ASYNC-CORE\n\n## What is a stream\n\nA stream of data is like a list, where the items come in not all at once, but one by one.\n\n## Creating a stream\n\nYou can create a stream either using one of the creation shortcuts from the StreamExtensions, or by using a Sink.\n\nTo use the StreamExtensions, add the following import:\n\n\timport static extension nl.kii.async.stream.StreamExtensions.*\n\nTo create a stream from any collection (list, queue, etc), stream the iterator:\n\n\tval s = #[1, 2, 3].iterator().stream()\n\nYou can also stream any range:\n\n\tval s = (1..1000).stream()\n\nYou can also create any stream using a Sink as follows:\n\n\tval sink = new Sink\u003cInteger\u003e {\n\t\toverride onNext() { }\n\t\toverride onClose() { }\n\t}\n\nor:\n\n\tval sink = newSink()\n\nA Sink is a Stream. Sink has a method *push(value)* that allows you to push something into the stream:\n\n\tsink.push(12)\n\nYou can also push in an error:\n\n\tsink.error(new Exception('something went wrong'))\n\nBy implementing onNext() and onClose() you can decide what should happen when sink.next and sink.close are called. \nThis allows the stream to be *controlled*, meaning that the listener of the stream can control when it gets a new value\nfrom the stream.\n\nFor example, to implement an iterator stream:\n\n\tval iterator = #[1, 2, 3].iterator()\n\tval sink = new Sink\u003cInteger\u003e {\n\t\toverride onNext() {\n\t\t\tif(iterator.hasNext) push(iterator.next()) \n\t\t\telse complete()\n\t\t}\n\t\toverride onClose() { }\n\t}\n\nSink.complete() tells the stream that there will be no more data coming, the set is completed.\n\n## Listening to a Stream using an Observer\n\nEvery stream is *Observable*. This means it exposes the method *Stream.observe(observer)*. \n\nAn *Observer* is an interface that lets you respond to a value from the stream, an error from the stream, \nand when the stream completes.\n\nFor example, to print all values coming from a stream:\n\n\tval stream = (1..3).stream()\n\tstream.observer = new Observer\u003cInteger\u003e {\n\t\toverride value(int in, int value) {\n\t\t\tprintln('got value ' + value)\n\t\t\tstream.next\n\t\t}\n\t\toverride error(int in, Throwable err) {\n\t\t\tprintln('error: ' + err.message)\n\t\t\tstream.next\n\t\t}\n\t\toverride complete() {\n\t\t\tprintln('done!')\n\t\t}\n\t}\n\tstream.next\n\nThis will print:\n\n\tgot value 1\n\tgot value 2\n\tgot value 3\n\tdone!\n\nThe .stream() method is an extension method from StreamExtensions that creates a \ncontrolled sink from a range, much like discussed above. By setting the observer\nto this sink (which implements *Stream*) we can then listen to values from the stream.\n\nNotice that we need to perform stream.next to get a next value from the stream, otherwise nothing happens! \nWe need to do this to get the first value or error, and again when we recieve a value.\n\n## Listening to a Stream using StreamExtensions\n\n*StreamExtensions* contains a lot of methods that make working with streams easier.\nWe can do what we did with the observable above using the extensions like this:\n\n\t(1..3).stream\n\t\t.effect [ println(‘got value’ + it) ]\n\t\t.on(Throwable) [ println(‘error: ‘ + message) ]\n\t\t.start\n\nThe *Stream.effect [ ]* method performs a side effect for each incoming value on the stream.\nThis is much like Iterable.onEach [ ], but for streams.\n\nThe *Stream.on(Throwable) [ ]* method performs a side effect when an error of the passed type\noccurs. In this case, we print the error message.\n\nFinally, the *Stream.start()* method does two things. First of all it will perform the stream.next\nto start the stream initially. Then for each incoming value, it will also call stream.next. In other\nwords, the start method starts off the stream and makes sure it keeps asking for the next value after\na value arrives.\n\n# XTEND-ASYNC-FIBERS\n\nFibers are like Threads: they let you do things in the background. The xtend-async-fibers project uses the [Quasar library](http://docs.paralleluniverse.co/quasar/).\n\nFibers are made for non-blocking code. They are great for processing a lot of parallel requests in the background, because unlike Threads, Fibers are really light and provide little overhead.\n\nIf you need to do heavy lifting, let a fiber delegate to a Thread pool instead.\n\nThe big benefit of using Fibers is that it lets you work with non-blocking asynchronous code as if it were blocking code. You can perform some asynchronous call that returns a Promise or Task, and simply wait for the result, without a closure. An error you can simply catch with a normal try/catch as well.\n\nHowever this waiting is non-blocking, what actually happens is that the code suspends when you do the await, and another Fiber can be run. This background magic is made possible using continuations, through byte code injection. For more information, see [what are fibers and why should you care](http://zeroturnaround.com/rebellabs/what-are-fibers-and-why-you-should-care/).\n\n## Usage\n\nSay that you have a method which loads a webpage and returns a promise of that page as a string:\n\n\thttploader.loadPage(String url) returns Promise\u003cString, String\u003e\n\nIf we normally want to use this and then print the result, we have to do something like this:\n\n\thttploader.loadPage('www.cnn.com')\n\t\t.then [ println('loaded page ' + it ]\n\t\t.on(Exception) [ println('something went wrong') ]\n\nIn other words, asynchronous code forces us to define handlers for both values and errors, and the next line of code is executed immediately.\n\n### Await\n\nThe xtend-async-fiber library lets you write this asynchronous code as if it were synchronous:\n\n\timport static extension nl.kii.async.fibers.FiberExtensions.*\n\t…\n\ttry {\n\t\tprintln(httploader.loadpage('www.cnn.com').await)\n\t} catch(Exception e) {\n\t\tprintln('something went wrong')\n\t}\n\nNote that this reuses the httploader.loadpage method, nothing needs to be changed in the existing codebase.\n\nThe change is the .await method, which takes any promise, waits for the result by blocking the current fiber, and when it has the result, returns that result. If the promise has an error, it will throw that error, so you can catch it using try / catch.\n\nWe can now also load many pages one by one:\n\n\tval pages = #{\n\t\t'cnn' -\u003e httploader.loadpage('www.cnn.com').await,\n\t\t'verge' -\u003e httploader.loadpage('www.theverge.com').await,\n\t\t'yahoo' -\u003e httploader.loadpage('www.yahoo.com').await \n\t}\n\tprintln(pages.get('verge'))\n\n### Async\n\nTo perform an operation in the background, use the async method. For example:\n\n\timport static extension nl.kii.async.fibers.FiberExtensions.*\n\t…\n\tasync [ 'this happens second' ]\n\tprintln('this happens first')\n\nThe async static method returns a Promise\u003c?, OUT\u003e where out is the return type of the closure. This means you can do this:\n\n\tval promise = async [ 1 + 1 ]\n\tpromise.then [ println(it) ]\n\tprintln('this happens before 2 gets printed')\n\nYou can use await to block the async process until you have the result you wanted:\n\n\tval task = async [ println('this happens second') ]\n\tprintln('this happens first')\n\tawait(task)\n\tprintln('this happens third')\n\nThe combination of async and await is powerful and gives you control over when you have what information.\n\n## Limitations and Requirements\n\nBytecode injection is necessary for the Fibers to run. To have this code injected, two things are required:\n\n1. All methods that contain suspendable code (in our case, code that calls .await) must either throw SuspendExecution or be annotated with @Suspendable. Any methods that use this method in turn must also be suspendable.\n2. When running the code, it must be instrumented. The easiest way to do this is with a java agent, which you provide to the JVM when starting the Java program. [More information here](http://docs.paralleluniverse.co/quasar/#instrumentation).\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblueneogeo%2Fxtend-async","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblueneogeo%2Fxtend-async","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblueneogeo%2Fxtend-async/lists"}