{"id":13990736,"url":"https://github.com/freshOS/Then","last_synced_at":"2025-07-22T13:31:18.206Z","repository":{"id":41474462,"uuid":"51218991","full_name":"freshOS/Then","owner":"freshOS","description":":clapper: Tame async code with battle-tested promises","archived":false,"fork":false,"pushed_at":"2024-03-06T08:29:14.000Z","size":79426,"stargazers_count":987,"open_issues_count":7,"forks_count":76,"subscribers_count":21,"default_branch":"master","last_synced_at":"2024-04-13T21:44:21.651Z","etag":null,"topics":["async","async-await","freshos","future","ios","micro-framework","promise","swift","task"],"latest_commit_sha":null,"homepage":"","language":"Swift","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/freshOS.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null},"funding":{"open_collective":"freshos","github":"s4cha"}},"created_at":"2016-02-06T19:57:52.000Z","updated_at":"2024-04-15T09:21:21.515Z","dependencies_parsed_at":"2024-04-15T09:21:20.691Z","dependency_job_id":"fb780357-319a-40af-aa62-3e7cd413f7ed","html_url":"https://github.com/freshOS/Then","commit_stats":{"total_commits":266,"total_committers":20,"mean_commits":13.3,"dds":"0.18045112781954886","last_synced_commit":"709112d17ac9f61060b28c86d61d3f0f9b66c08a"},"previous_names":[],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshOS%2FThen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshOS%2FThen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshOS%2FThen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshOS%2FThen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/freshOS","download_url":"https://codeload.github.com/freshOS/Then/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":214668598,"owners_count":15767199,"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":["async","async-await","freshos","future","ios","micro-framework","promise","swift","task"],"created_at":"2024-08-09T13:03:10.007Z","updated_at":"2024-08-09T13:11:16.341Z","avatar_url":"https://github.com/freshOS.png","language":"Swift","readme":"![Then](https://raw.githubusercontent.com/freshOS/then/master/banner.png)\n\n# Then\n[![Language: Swift 5](https://img.shields.io/badge/language-swift5-f48041.svg?style=flat)](https://developer.apple.com/swift)\n![Platform: iOS 8+/macOS10.11](https://img.shields.io/badge/platform-iOS|macOS|tvOS|watchOS-blue.svg?style=flat)\n[![SPM compatible](https://img.shields.io/badge/SPM-compatible-4BC51D.svg?style=flat)](https://swift.org/package-manager/)\n[![Build Status](https://app.bitrise.io/app/c6b39aea308618bf/status.svg?token=11AKF3ZUtPtNoXE9Lnk62A\u0026branch=master)](https://app.bitrise.io/app/c6b39aea308618bf)\n[![License: MIT](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/freshOS/then/blob/master/LICENSE)\n ![Release version](https://img.shields.io/github/v/release/freshos/then.svg)\n\n[Reason](#why) - [Example](#example) -  [Documentation](#documentation) - [Installation](#installation)\n\n```swift\nfetchUserId().then { id in\n    print(\"UserID : \\(id)\")\n}.onError { e in\n    print(\"An error occured : \\(e)\")\n}.finally {\n    print(\"Everything is Done :)\")\n}\n```\n\n```swift\n  let userId = try! awaitPromise(fetchUserId())\n```\n\nBecause async code is hard to write, hard to read, hard to reason about.   **A pain to maintain**\n\n## Try it\nthen is part of [freshOS](https://freshos.github.io/) iOS toolset. Try it in an example App! \u003ca class=\"github-button\" href=\"https://github.com/freshOS/StarterProject/archive/master.zip\" data-icon=\"octicon-cloud-download\" data-style=\"mega\" aria-label=\"Download freshOS/StarterProject on GitHub\"\u003eDownload Starter Project\u003c/a\u003e\n\n## How\nBy using a **then** keyword that enables you to write aSync code that *reads like an English sentence*  \nAsync code is now **concise**, **flexible** and **maintainable** ❤️\n\n## What\n- [x] Based on the popular `Promise` / `Future` concept\n- [x] `Async` / `Await`\n- [x] `progress` `race` `recover` `validate` `retry` `bridgeError` `chain` `noMatterWhat` ...\n- [x] Strongly Typed\n- [x] Pure Swift \u0026 Lightweight\n\n## Example\n### Before\n```swift\nfetchUserId({ id in\n    fetchUserNameFromId(id, success: { name in\n        fetchUserFollowStatusFromName(name, success: { isFollowed in\n            // The three calls in a row succeeded YAY!\n            reloadList()\n        }, failure: { error in\n            // Fetching user ID failed\n            reloadList()\n        })\n    }, failure: { error in\n        // Fetching user name failed\n        reloadList()\n    })\n}) {  error in\n    // Fetching user follow status failed\n    reloadList()\n}\n🙉🙈🙊#callbackHell\n```\n\n----\n\n### After\n\n```swift\nfetchUserId()\n    .then(fetchUserNameFromId)\n    .then(fetchUserFollowStatusFromName)\n    .then(updateFollowStatus)\n    .onError(showErrorPopup)\n    .finally(reloadList)\n```\n## 🎉🎉🎉\n\n\n## Going further 🤓\n\n```swift\nfetchUserId().then { id in\n    print(\"UserID : \\(id)\")\n}.onError { e in\n    print(\"An error occured : \\(e)\")\n}.finally {\n    print(\"Everything is Done :)\")\n}\n```\n\nIf we want this to be **maintainable**, it should read *like an English sentence*  \nWe can do this by extracting our blocks into separate functions:\n\n```swift\nfetchUserId()\n    .then(printUserID)\n    .onError(showErrorPopup)\n    .finally(reloadList)\n```\n\nThis is now **concise**, **flexible**, **maintainable**, and it reads like an English sentence \u003c3  \nMental sanity saved\n// #goodbyeCallbackHell\n\n\n## Documentation\n\n1. [Writing your own Promise](#writing-your-own-promise-💪)\n2. [Progress](#progress)\n3. [Registering a block for later](#registering-a-block-for-later)\n4. [Returning a rejecting promise](#returning-a-rejecting-promise)\n5. [Common Helpers](#common-helpers)\n  1. [race](#race)\n  2. [recover](#recover)\n  3. [validate](#validate)\n  4. [retry](#retry)\n  5. [bridgeError](#bridgeError)\n  6. [whenAll](#whenAll)\n  7. [chain](#chain)\n  8. [noMatterWhat](#nomatterwhat)\n  9. [unwrap](#unwrap)\n6. [AsyncTask](#asynctask)\n7. [Async/Await](#async/await)\n\n### Writing your own Promise 💪\nWondering what fetchUserId() is?  \nIt is a simple function that returns a strongly typed promise :\n\n```swift\nfunc fetchUserId() -\u003e Promise\u003cInt\u003e {\n    return Promise { resolve, reject in\n        print(\"fetching user Id ...\")\n        wait { resolve(1234) }\n    }\n}\n```\nHere you would typically replace the dummy wait function by your network request \u003c3\n\n### Progress\n\nAs for `then` and `onError`, you can also call a `progress` block for things like uploading an avatar for example.\n\n```swift\nuploadAvatar().progress { p in\n  // Here update progressView for example\n}\n.then(doSomething)\n.onError(showErrorPopup)\n.finally(doSomething)\n```\n\n### Registering a block for later\nOur implementation slightly differs from the original javascript Promises. Indeed, they do not start right away, on purpose. Calling `then`, `onError`, or `finally` will start them automatically.\n\nCalling `then` starts a promise if it is not already started.\nIn some cases, we only want to register some code for later.\nFor instance, in the case of JSON to Swift model parsing, we often want to attach parsing blocks to JSON promises, but without starting them.\n\nIn order to do that we need to use `registerThen` instead. It's the exact same thing as `then` without starting the promise right away.\n\n```swift\nlet fetchUsers:Promise\u003c[User]\u003e = fetchUsersJSON().registerThen(parseUsersJSON)\n\n// Here promise is not launched yet \\o/\n\n// later...\nfetchUsers.then { users in\n    // YAY\n}\n```\nNote that `onError` and `finally` also have their non-starting counterparts : `registerOnError` and `registerFinally`.\n\n### Returning a rejecting promise\n\nOftetimes we need to return a rejecting promise as such :\n\n```swift\nreturn Promise { _, reject in\n  reject(anError)\n}\n```\n\nThis can be written with the following shortcut :\n```swift\nreturn Promise.reject(error:anError)\n```\n\n### Common Helpers\n\n#### Race\n\nWith `race`, you can send multiple tasks and get the result of the first one coming back :\n```swift\nrace(task1, task2, task3).then { work in\n  // The first result !\n}\n```\n\n#### Recover\n\nWith `.recover`, you can provide a fallback value for a failed Promise.  \nYou can :\n  - Recover with a value\n  - Recover with a value for a specific Error type\n  - Return a value from a block, enabling you to test the type of error and return distinct values.\n  - Recover with another Promise with the same Type\n\n```swift\n.recover(with: 12)\n.recover(MyError.defaultError, with: 12)\n.recover { e in\n  if e == x { return 32 }\n  if e == y { return 143 }\n  throw MyError.defaultError\n}\n.recover { e -\u003e Promise\u003cInt\u003e in\n  // Deal with the error then\n  return Promise\u003cInt\u003e.resolve(56)\n  // Or\n  return Promise\u003cInt\u003e.reject(e)\n  }\n}\n.recover(with: Promise\u003cInt\u003e.resolve(56))\n```\nNote that in the block version you can also throw your own error \\o/\n\n\n#### Validate\n\nWith `.validate`, you can break the promise chain with an assertion block.\n\nYou can:\n  - Insert assertion in Promise chain\n  - Insert assertion and return you own Error\n\nFor instance checking if a user is allowed to drink alcohol :\n```swift\nfetchUserAge()\n.validate { $0 \u003e 18 }\n.then { age in\n  // Offer a drink\n}\n\n.validate(withError: MyError.defaultError, { $0 \u003e 18 })`\n```\nA failed validation will retrun a `PromiseError.validationFailed` by default.\n\n#### Retry\n\nWith `retry`, you can restart a failed Promise X number of times.\n```swift\ndoSomething()\n  .retry(10)\n  .then { v in\n   // YAY!\n  }.onError { e in\n    // Failed 10 times in a row\n  }\n```\n\n#### BridgeError\n\nWith `.bridgeError`, you can intercept a low-level Error and return your own high level error.\nThe classic use-case is when you receive an api error and you bridge it to your own domain error.\n\nYou can:\n  - Catch all errors and use your own Error type\n  - Catch only a specific error\n\n```swift\n.bridgeError(to: MyError.defaultError)\n.bridgeError(SomeError, to: MyError.defaultError)\n```\n\n#### WhenAll\n\nWith `.whenAll`, you can combine multiple calls and get all the results when all the promises are fulfilled :\n\n```swift\nwhenAll(fetchUsersA(),fetchUsersB(), fetchUsersC()).then { allUsers in\n  // All the promises came back\n}\n```\n\n#### Chain\n\nWith `chain`, you can add behaviours without changing the chain of Promises.\n\nA common use-case is for adding Analytics tracking like so:\n\n```swift\nextension Photo {\n    public func post() -\u003e Async\u003cPhoto\u003e {\n        return api.post(self).chain { _ in\n            Tracker.trackEvent(.postPicture)\n        }\n    }\n}\n```\n\n#### NoMatterWhat\n\nWith `noMatterWhat` you can add code to be executed in the middle of a promise chain, no matter what happens.\n\n```swift\nfunc fetchNext() -\u003e Promise\u003c[T]\u003e {\n    isLoading = true\n    call.params[\"page\"] = page + 1\n    return call.fetch()\n        .registerThen(parseResponse)\n        .resolveOnMainThread()\n        .noMatterWhat {\n            self.isLoading = false\n    }\n}\n```\n\n#### Unwrap\n\nWith `unwrap` you can transform an optional into a promise :\n\n```swift    \nfunc fetch(userId: String?) -\u003e Promise\u003cVoid\u003e {\n   return unwrap(userId).then {\n        network.get(\"/user/\\($0)\")\n    }\n}\n```\nUnwrap will fail the promise chain with `unwrappingFailed` error in case of a nil value :)\n\n### AsyncTask\n`AsyncTask` and `Async\u003cT\u003e` typealisases are provided for those of us who think that Async can be clearer than `Promise`.\nFeel free to replace `Promise\u003cVoid\u003e` by `AsyncTask` and `Promise\u003cT\u003e` by `Async\u003cT\u003e` wherever needed.  \nThis is purely for the eyes :)\n\n\n### Async/Await\n\n`awaitPromise` waits for a promise to complete synchronously and yields the result :\n\n```swift\nlet photos = try! awaitPromise(getPhotos())\n```\n\n`async` takes a block and wraps it in a background Promise.\n\n```swift\nasync {\n  let photos = try awaitPromise(getPhotos())\n}\n```\nNotice how we don't need the `!` anymore because `async` will catch the errors.\n\n\nTogether, `async`/`awaitPromise` enable us to write asynchronous code in a synchronous manner :\n\n```swift\nasync {\n  let userId = try awaitPromise(fetchUserId())\n  let userName = try awaitPromise(fetchUserNameFromId(userId))\n  let isFollowed = try awaitPromise(fetchUserFollowStatusFromName(userName))\n  return isFollowed\n}.then { isFollowed in\n  print(isFollowed)\n}.onError { e in\n  // handle errors\n}\n```\n\n#### Await operators\nAwait comes with `..` shorthand operator. The `..?` will fallback to a nil value instead of throwing.\n```swift\nlet userId = try awaitPromise(fetchUserId())\n```\nCan be written like this:\n```swift\nlet userId = try ..fetchUserId()\n```\n\n\n## Installation\n\nThe Swift Package Manager (SPM) is now the official way to install `Then`. The other package managers are now deprecated as of `5.1.3` and won't be supported in future versions.\n\n### Swift Package Manager\n\n`Xcode` \u003e `File` \u003e `Swift Packages` \u003e `Add Package Dependency...` \u003e `Paste` `https://github.com/freshOS/Then`\n\n### Cocoapods - Deprecated\n```swift\ntarget 'MyApp'\npod 'thenPromise'\nuse_frameworks!\n```\n\n### Carthage  - Deprecated\n```\ngithub \"freshOS/then\"\n```\n\n## Contributors\n\n[S4cha](https://github.com/S4cha), [Max Konovalov](https://github.com/maxkonovalov), [YannickDot](https://github.com/YannickDot), [Damien](https://github.com/damien-nd),\n[piterlouis](https://github.com/piterlouis)\n\n## Swift Version\n\n- Swift 2 -\u003e version [**1.4.2**](https://github.com/freshOS/then/releases/tag/1.4.2)\n- Swift 3 -\u003e version [**2.2.5**](https://github.com/freshOS/then/releases/tag/2.2.5)\n- Swift 4 -\u003e version [**3.1.0**](https://github.com/freshOS/then/releases/tag/3.1.0)\n- Swift 4.1 -\u003e version [**4.1.1**](https://github.com/freshOS/then/releases/tag/4.1.1)\n- Swift 4.2 -\u003e version [**4.2.0**](https://github.com/freshOS/then/releases/tag/4.2.0)\n- Swift 4.2.1 -\u003e version [**4.2.0**](https://github.com/freshOS/then/releases/tag/4.2.1)  \n- Swift 5.0 -\u003e version [**5.0.0**](https://github.com/freshOS/then/releases/tag/5.0.0)\n- Swift 5.1 -\u003e version [**5.1.0**](https://github.com/freshOS/then/releases/tag/5.1.0)\n- Swift 5.1.3 -\u003e version [**5.1.2**](https://github.com/freshOS/then/releases/tag/5.1.2)\n\n\n\n### Backers\nLike the project? Offer coffee or support us with a monthly donation and help us continue our activities :)\n\n\u003ca href=\"https://opencollective.com/freshos/backer/0/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/0/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/1/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/1/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/2/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/2/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/3/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/3/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/4/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/4/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/5/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/5/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/6/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/6/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/7/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/7/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/8/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/8/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/9/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/9/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/10/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/10/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/11/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/11/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/12/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/12/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/13/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/13/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/14/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/14/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/15/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/15/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/16/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/16/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/17/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/17/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/18/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/18/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/19/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/19/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/20/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/20/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/21/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/21/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/22/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/22/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/23/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/23/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/24/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/24/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/25/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/25/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/26/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/26/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/27/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/27/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/28/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/28/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/backer/29/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/backer/29/avatar.svg\"\u003e\u003c/a\u003e\n\n### Sponsors\nBecome a sponsor and get your logo on our README on Github with a link to your site :)\n\n\u003ca href=\"https://opencollective.com/freshos/sponsor/0/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/0/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/1/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/1/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/2/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/2/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/3/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/3/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/4/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/4/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/5/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/5/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/6/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/6/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/7/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/7/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/8/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/8/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/9/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/9/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/10/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/10/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/11/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/11/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/12/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/12/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/13/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/13/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/14/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/14/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/15/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/15/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/16/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/16/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/17/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/17/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/18/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/18/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/19/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/19/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/20/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/20/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/21/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/21/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/22/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/22/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/23/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/23/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/24/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/24/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/25/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/25/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/26/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/26/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/27/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/27/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/28/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/28/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/freshos/sponsor/29/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/freshos/sponsor/29/avatar.svg\"\u003e\u003c/a\u003e\n","funding_links":["https://opencollective.com/freshos","https://github.com/sponsors/s4cha","https://opencollective.com/freshos/backer/0/website","https://opencollective.com/freshos/backer/1/website","https://opencollective.com/freshos/backer/2/website","https://opencollective.com/freshos/backer/3/website","https://opencollective.com/freshos/backer/4/website","https://opencollective.com/freshos/backer/5/website","https://opencollective.com/freshos/backer/6/website","https://opencollective.com/freshos/backer/7/website","https://opencollective.com/freshos/backer/8/website","https://opencollective.com/freshos/backer/9/website","https://opencollective.com/freshos/backer/10/website","https://opencollective.com/freshos/backer/11/website","https://opencollective.com/freshos/backer/12/website","https://opencollective.com/freshos/backer/13/website","https://opencollective.com/freshos/backer/14/website","https://opencollective.com/freshos/backer/15/website","https://opencollective.com/freshos/backer/16/website","https://opencollective.com/freshos/backer/17/website","https://opencollective.com/freshos/backer/18/website","https://opencollective.com/freshos/backer/19/website","https://opencollective.com/freshos/backer/20/website","https://opencollective.com/freshos/backer/21/website","https://opencollective.com/freshos/backer/22/website","https://opencollective.com/freshos/backer/23/website","https://opencollective.com/freshos/backer/24/website","https://opencollective.com/freshos/backer/25/website","https://opencollective.com/freshos/backer/26/website","https://opencollective.com/freshos/backer/27/website","https://opencollective.com/freshos/backer/28/website","https://opencollective.com/freshos/backer/29/website"],"categories":["Swift","task"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FfreshOS%2FThen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FfreshOS%2FThen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FfreshOS%2FThen/lists"}