{"id":19260884,"url":"https://github.com/evant/ipromise","last_synced_at":"2025-07-28T08:35:35.349Z","repository":{"id":11454170,"uuid":"13915563","full_name":"evant/ipromise","owner":"evant","description":"small proimse/future library for java and Android","archived":false,"fork":false,"pushed_at":"2020-06-14T22:48:46.000Z","size":855,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-05T09:44:31.687Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/evant.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-10-28T03:07:27.000Z","updated_at":"2016-11-16T16:21:36.000Z","dependencies_parsed_at":"2022-08-02T15:15:12.322Z","dependency_job_id":null,"html_url":"https://github.com/evant/ipromise","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/evant%2Fipromise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evant%2Fipromise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evant%2Fipromise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evant%2Fipromise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/evant","download_url":"https://codeload.github.com/evant/ipromise/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240357589,"owners_count":19788735,"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-11-09T19:23:21.709Z","updated_at":"2025-02-23T18:25:14.774Z","avatar_url":"https://github.com/evant.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"ipromise\n======\nWriting asynchronous code can quickly become painful, especially when you need\nto make multiple calls, due to each method taking it's own callback and having\nit's own idea of error handling and cancellation. Promises alleviate this by\nproviding a uniform interface and by turning callback style into the same return\nstyle of synchronous code. You can view the javadoc at\nhttp://evant.github.com/ipromise\n\n- [Install](#install)\n- [Features](#features)\n- [Usage](#usage)\n- [Implementing Promises](#implementing-promises)\n- [Progress](#progress)\n- [Cancellation](#cancellation)\n- [Callback Execution](#callback-execution)\n- [Android](#android)\n- [A Note on Memory Usage](#a-note-on-memory-usage)\n\nInstall\n-----------\nClone this library, then run\n```bash\ngradle install\n```\n\n### Gradle\n```groovy\nrepositories {\n  mavenLocal()\n}\n\ndependencies {\n  compile 'me.tatarka.ipromise:ipromise:1.0-SNAPSHOT'\n}\n```\n\nOr for Android\n```groovy\ndependencies {\n  compile 'me.tatarka.ipromise:ipromise-android:1.0-SNAPSHOT'\n}\n```\n\n### Maven\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003eme.tatarka.ipromise\u003c/groupId\u003e\n  \u003cartifactId\u003eipromise\u003c/artifactId\u003e\n  \u003cversion\u003e1.0-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nOr for Android\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003eme.tatarka.ipromise\u003c/groupId\u003e\n  \u003cartifactId\u003eipromise-android\u003c/artifactId\u003e\n  \u003cversion\u003e1.0-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nFeatures\n--------\nThis library has a few unique features to give you control over your\nasynchronous code.\n\n1. You can send multiple messages to show progress, etc.\n2. Error control is out not baked in to the promises. Instead you can\nrepresent errors using a `Result\u003cT,E\u003e`. This way you are free to use promises\nin contexts where you know an Exception will never be thrown.\n3. You can control how messages are buffered for redelivery when a listener is\nattached. The default is just to save the last one, but you can save all of\nthem, none of them, or implement your own scheme. (see `PromiseBuffer`).\n4. You can control in what context callbacks are executed. By default they are\nexecuted on a single background thread; but you can, for example, run them on\nyour UI thread so you don't have to worry about posting them back.\n5. Special consideration has been given to Android to handle the Activity\nlifecycle so you don't run into the same pitfalls as you do with `AsyncTask`\n\n\nUsage\n-----\nYou can turn this:\n\n```java\nasync1(arg, new Callback1() {\n    @Override\n\tpublic void onResult(MyResult1 result1) {\n\t\tasync2(result1, new Callback2() {\n\t\t\t@Override\n\t\t\tpublic void onResult(MyResult2 result2) {\n\t\t\t\t// Finally! do something with result2.\n\t\t\t}\n\t\t}\n\t}\n}\n```\nInto this:\n```java\nasync1(arg).then(new Chain\u003cMyResult1, Promise\u003cMyResult2\u003e\u003e() {\n\t@Override\n\tpublic Promise\u003cMyResult2\u003e chain(MyResult1 result1) {\n\t\treturn async2(result2);\n\t}\n}).listen(new Promise.Listener\u003cMyResult2\u003e() {\n\t@Override\n\tpublic void receive(MyResult2 result) {\n\t\t// Isn't this nicer?\n\t}\n});\n```\nSee how it reads nice and sequentially instead of heavily indented?  If that\ndoesn't look like a huge improvement to you, lets add some error checking. After\nall, `async1()` and `async2()` may fail.\n\n```java\nasync1(arg, new Callback1() {\n\t@Override\n\tpublic void onResult(MyResult1 result1) {\n\t\tasync2(result1, new Callback2() {\n\t\t\t@Override\n\t\t\tpublic void onResult(MyResult2 result2) {\n\t\t\t\t// Finally! do something with result2.\n\t\t\t}\n\t\t\t@Override\n\t\t\tpublic void onError(Error error) {\n\t\t\t\t// Take care of the error on the second callback\n\t\t\t}\n\t\t}\n\t}\n\t@Override\n\tpublic void onError(Error error) {\n\t\t// Take care of the error on the first callback\n\t}\n}\n```\nSee how the error handling is all over the place? And you would have duplicate\ncode if your error handling in both places was the same.\n```java\nasync1(arg).then(new Result.ChainPromise\u003cMyResult1, MyResult2, Error\u003e() {\n\t@Override\n\tpublic Promise\u003cResult\u003cMyResult2, Error\u003e\u003e success(MyResult1 result1) {\n\t\treturn async2(result1);\n\t}\n}).listen(new Result.Listener\u003cMyResult2, Error\u003e() {\n\t@Override\n\tpublic void success(MyResult2 result) {\n\t\t// Isn't this nicer?\n\t}\n\t@Override\n\tpublic void error(Error error) {\n\t\t// Error checking in one place!\n\t}\n});\n```\nMuch nicer! Now lets say the user decides they don't want to wait anymore and\ncancels the action.\n```java\n// The library I'm using doesn't have a way to cancel method calls :(\nboolean isCanceled = false;\n\nasync1(arg, new Callback1() {\n\t@Override\n\tpublic void onResult(MyResult1 result1) {\n\t\tif (isCanceled) return; // Easy to forget one of these\n\t\tasync2(result1, new Callback2() {\n\t\t\t@Override\n\t\t\tpublic void onResult(MyResult2 result2) {\n\t\t\t\tif (isCanceled) return;\n\t\t\t\t// Finally! do something with result2.\n\t\t\t}\n\t\t\t@Override\n\t\t\tpublic void onError(Error error) {\n\t\t\t\t// Take care of the error on the second callback\n\t\t\t}\n\t\t}\n\t}\n\t@Override\n\tpublic void onError(Error error) {\n\t\t// Take care of the error on the first callback\n\t}\n}\n\npublic void userCancel() {\n\tisCanceled = true;\n}\n```\nThis code is starting to look like a mess! With promises it's so much easier:\n```java\nPromise\u003cResult\u003cResult2, Error\u003e\u003e promise = async1(arg).then(new Result.ChainPromise\u003cResult1, Result2, Error\u003e() {\n\t@Override\n\tpublic Promise\u003cResult\u003cResult2, Error\u003e\u003e success(Result1 result1) {\n\t\treturn async2(result1);\n\t}\n}).listen(new Result.Listener\u003cResult2, Error\u003e() {\n\t@Override\n\tpublic void success(Result2 result) {\n\t\t// Isn't this nicer?\n\t}\n\t@Override\n\tpublic void error(Error error) {\n\t\t// Error checking in one place!\n\t}\n});\n\npublic void userCancel() {\n\tpromise.cancel(); // That's it!\n}\n```\nAnd if the library does support cancellation of methods, it can just listen for\na cancellation on the promise.\n\nImplementing Promises\n---------------------\nSo you have lots of code lying around that uses callbacks? Wrapping it up is\nsuper easy.\n```java\npublic void asyncWithCallback(Arg arg, Callback callback) {\n\t...\n}\n\npublic Promise\u003cResult\u003cMyResult, Error\u003e\u003e asyncWithPromise(Arg arg) {\n\tfinal Deferred\u003cResult\u003cMyResult, Error\u003e\u003e deferred = new Deferred\u003cResult\u003cMyResult, Error\u003e\u003e();\n\tasyncWithCallback(arg, new Callback() {\n\t\t@Override\n\t\tpublic void onResult(MyResult result) {\n\t\t\tdeferred.resolve(Result.\u003cMyResult, Error\u003esuccess(result));\n\t\t}\n\t\t@Override\n\t\tpublic void onError(Error error) {\n\t\t\tdeferred.resolve(Result.\u003cMyResult, Error\u003eerror(error));\n\t\t}\n\t});\n\treturn deferred.promise();\n}\n```\n\nYou can also use a `Task` to easily run code in a separate thread and return a\n`Proimse`.\n```java\n// Runs in a seperate thread \npublic Proimse\u003cMyResult\u003e async() {\n  return Tasks.run(new Task.DoOnce\u003cMyResult\u003e() {\n    @Override\n    public MyResult runOnce(CancelToken cancelToken) {\n      return sync();\n    }\n  });\n}\n\n// Handles exceptions \npublic Promise\u003cResult\u003cMyResult, Error\u003e\u003e asyncError() {\n  return Tasks.run(new Task.DoOnceFailable\u003cMyResult, Error\u003e() {\n    @Override\n    public MyResult runFailable(CancelToken cancelToken) throws Error {\n      return syncThatThrows();\n    }\n  });\n}\n```\n\nYou can also pass an `Executor` to a `Task` for more control.\n\nProgress\n--------\nIf you have to return multiple results over time, just use `send()` instead of\n`resolve()`. You do, however need to make sure to call `close()` when you are\nfinished sending. \n\n```java\npublic Promise\u003cMyProgress\u003e asyncWithPromise(Arg arg) {\n  final Deferred\u003cMyProgress\u003e deferred = new Deferred\u003cMyProgress\u003e();\n  asyncWithProgressCallback(arg, new Callback() {\n    @Override\n    public void onProgress(MyProgress progress) {\n\t\t  deferred.send(progress);\n\t\t}\n\t\t@Override\n\t\tpublic void onFinish() {\n\t\t  deferred.close();\n\t\t}\n\t});\n\treturn channel.progress();\n}\n```\n\nOn the receiving end, you can attach a `CloseListener` to respond to when the\npromise has been closed. \n\n```java\npromise.listen(new Listener\u003cMyProgress\u003e() {\n  @Override\n  public void receive(MyProgress progress) {\n    // This is called with every progress update.\n  }\n}).onClose(new CloseListener() {\n  @Override\n  public void close() {\n    // This is called when there will be no more updates.\n  }\n});\n```\n\nThere are various different ways to handle old messages when multiple ones are\nsent. For this reason, you can pass a `PromiseBuffer` to a `Deferred` which will\ndetermine how messages are buffered and redelivered when a listener is attached.\n\nFor the common cases you can pass in an enumeration.\n\n- `Proimse.BUFFER_NONE` - This means that if there is no listener when a message\n  is sent, the message is dropped.\n- `Promise.BUFFER_LAST` - This means the last message is saved. This is the\n  default since it acts like most promise implementations when one message is\n  sent.\n- `Progress.REATIN_ALL` - This means all messages are saved.\n\nYou can also pass in your own instance of `PromiseBuffer` if you need more\ncontrol.\n\nWhen choosing a buffer strategy keep in mind that all messages saved will be\nkept in memory until the `Promise` is garbage collected.\n\nCancellation\n------------\nIf you have or want to create asynchronous methods that support cancellation,\nyou need to use a `CancelToken`. This ensures the cancel propagates to all\nPromises.\n```java\npublic Promise\u003cInteger\u003e mySuperSlowMethod() {\n\tfinal CancelToken cancelToken = new CancelToken();\n\tfinal Deferred\u003cInteger\u003e deferred = new Deferred\u003cInteger\u003e(cancelToken);\n\tnew Thread(new Runnable() {\n\t\tlong total = 0;\n\t\tfor (long i = 0; i \u003c BAZZILION; i++) {\n\t\t\tif (cancelToken.isCanceled()) break;\n\t\t\ttotal += i; // Do some hard work\n\t\t}\n\t\tdeferred.resolve(total);\n\t}).start();\n\treturn deferred.promise();\n}\n\npublic Promise\u003cMyResult\u003e yourSuperSlowMethod() {\n\tfinal CancelToken cancelToken = new CancelToken();\n\tfinal Deferred\u003cMyResult\u003e deferred = new Deferred\u003cMyResult\u003e(cancelToken);\n\tfinal Callback callback = new Callback() {\n\t\t@Override\n\t\tpublic void onResult(MyResult result) {\n\t\t\tdeferred.resolve(result);\n\t\t}\n\t});\n\tcancelToken.listen(new CancelToken.Listener() {\n\t\t@Override\n\t\tpublic void canceled() {\n\t\t\tmethodQueue.cancel(callback);\n\t\t}\n\t});\n\tmethodQueue.add(callback);\n\tmethodQueue.start();\n\treturn deferred.promise();\n}\n```\n\nCallback Execution\n------------------\nAs mentioned in the feature section, callbacks are not executed in the calling\ncontext, but instead by using an `Executor`. This makes them much easier to\nreason about since their execution context is always the same. By default all\ncallbacks are executed on a single background thread, but you can configure this\nis a few different ways.\n\nThe easiest way is to call `CallbackExecutors.setDefault(executor)` which sets\nit globally for all promises. This should only be called once for you\napplication, though this is not enforced. If you need more granular control you\ncan create a builder with `Deferred.Builder.withCallbackExecutor(executor)` and\nthen call `build()` to get a deferred with your `Executor`. Finally, `Deferred`\nhas a constructor that takes an `Executor` as an argument.\n\nWhen using a custom `Executor`, you must ensure that all jobs are run in the\nsame order they are posted for a given `Promise`. If you don't you risk the\ncallbacks being fired in an unexpected order.\n\nFor Android, it's a good idea to use the provided\n`AndroidCallbackExecutors.mainLooperExecutor()` which will run all\ncallbacks on the UI thread.\n\nFor unit testing, you can use `CallbackExecutors.sameThreadExecutor()` to run\ncallbacks on the same thread as the messages that are sent. This way you can\nsend a message and ensure the callback is called without using any thread\nsynchronization.\n\nAndroid\n-------\nManaging the Activity lifecycle in Android with asynchronous calls can be very\ntricky. Loaders improve this, but fall short in many situations and have a\nclunky api. Instead, you can use the `AsyncManager`. It will handle Activity\ndestruction, configuration changes, and posting results to the UI thread.\n\nIf you just want to load data in the background and show it on screen when you\nare done, it is incredibly easy.\n```java\npublic class MyActivity extends Activity {\n  private AsyncManager asyncManager;\n\n  public void onCreate(Bundle savedInstanceState) {\n    // If you are using the support library, use AsyncManagerCompat.get(this)\n    asyncManager = AsyncManager.get(this);\n    asyncManager.start(Task.of(mySlowTask), new AsyncAdapter\u003cString\u003e() {\n      @Override\n      public void start() {\n        // This is where you would show your progress indicator. This is called\n        // when the promise starts, and on configuration change if the promise\n        // hasn't completed.\n        findViewById(R.id.progress).setVisibility(View.VISIBLE);\n      }\n\n      @Override\n      public void receive(String result) {\n        // This is where you will get your result. This is called when the\n        // promise completes on configuration changes if the promise has\n        // completed.\n        findViewById(R.id.progress).setVisibility(View.INVISIBLE);\n      }\n    });\n  }\n\n  // It is important that this does not have a reference to surrounding Activity\n  // to prevent memory leaks\n  private static Task.DoOnce\u003cString\u003e mySlowTask = new Task.DoOnce\u003cString\u003e() {\n    @Override\n    public String run(CancelToken cancelToken) {\n      return doSomeSlowWork();\n    }\n  };\n}\n```\n\nLoading/Reloading on a button press is similarly easy. Try doing that with\nloaders!\n```java\npublic clas MyActivity extends Activity {\n  // You can pass a tag to the PromiseManager to allow multiple tasks/callbacks\n  // If you don't pass one, PromiseManager.DEFAULT is used.\n  private static final String MY_TASK = \"my_task\"\n\n  private AsyncManager asyncManager;\n\n  public void onCreate(Bundle savedInstanceState) {\n    asyncManager = AsyncManager.get(this);\n    final AsyncItem buttonAsync = asyncManager.add(\n      MY_TASK, \n      new Task\u003cString\u003e() {\n        @Override\n        public Promise\u003cString\u003e start() {\n          return doAsyncWork();\n        }\n      },\n      new AsyncAdapter\u003cString\u003e() {\n        @Override\n        public void start() {\n          findViewById(R.id.progress).setVisibility(View.VISIBLE);\n        }\n\n        @Override\n        public void receive(String result) {\n          findViewById(R.id.progress).setVisibility(View.INVISIBLE);\n        }\n      }\n    );\n\n    findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {\n      @Override\n      public void onClick(View view) {\n        buttonAsync.restart();\n      }\n    }\n  }\n}\n}\n```\n\nYou can see more examples on how to use `AsyncManager` in\n`ipromse-android-example`\n\nA Note on Memory Usage\n----------------------\nThis library does it's best effort to reduce memory usage by removing listeners \nwhen they are no longer needed and only keeping one copy of the messages in the\nbuffer even when you `Map` or `Filter`. However, you need to keep in mind a few\nthings to make sure you don't leak any memory.\n\nBy default, a `Promise` will store it's last result for as long is it lives.\nYou can configure this by passing a different `PromiseBuffer` to the `Deferred`.\nThis means that you should not keep a reference `Promise` or `Deferred` longer\nthan necessary.\n\nYou should also remember that anonymous inner classes (which you are probably\nusing for callbacks) will keep a reference to their outer class even if you\ndon't reference anything from it. This means that the outer class will not be\ngarbage collected until the `Promise` is closed or canceled.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevant%2Fipromise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fevant%2Fipromise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevant%2Fipromise/lists"}