├── .editorconfig ├── .github └── workflows │ ├── ci.yml │ └── deploy-docs.yml ├── .gitignore ├── .npmignore ├── .watchmanconfig ├── CHANGELOG.md ├── CNAME ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── UPGRADING-2.x.md ├── package.json ├── packages ├── ember-concurrency │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc.js │ ├── .template-lintrc.js │ ├── addon-main.cjs │ ├── async-arrow-task-transform.js │ ├── babel.config.json │ ├── package.json │ ├── rollup.config.mjs │ ├── src │ │ ├── -private │ │ │ ├── async-arrow-runtime.js │ │ │ ├── cancelable-promise-helpers.js │ │ │ ├── ember-environment.js │ │ │ ├── external │ │ │ │ ├── environment.js │ │ │ │ ├── generator-state.js │ │ │ │ ├── scheduler │ │ │ │ │ ├── policies │ │ │ │ │ │ ├── bounded-policy.js │ │ │ │ │ │ ├── drop-policy.js │ │ │ │ │ │ ├── enqueued-policy.js │ │ │ │ │ │ ├── execution-states.js │ │ │ │ │ │ ├── keep-latest-policy.js │ │ │ │ │ │ ├── restartable-policy.js │ │ │ │ │ │ └── unbounded-policy.js │ │ │ │ │ ├── refresh.js │ │ │ │ │ ├── scheduler.js │ │ │ │ │ └── state-tracker │ │ │ │ │ │ ├── null-state-tracker.js │ │ │ │ │ │ ├── null-state.js │ │ │ │ │ │ ├── state-tracker.js │ │ │ │ │ │ └── state.js │ │ │ │ ├── task-decorators.js │ │ │ │ ├── task-factory.js │ │ │ │ ├── task-instance │ │ │ │ │ ├── base.js │ │ │ │ │ ├── cancelation.js │ │ │ │ │ ├── completion-states.js │ │ │ │ │ ├── executor.js │ │ │ │ │ └── initial-state.js │ │ │ │ ├── task │ │ │ │ │ ├── default-state.js │ │ │ │ │ ├── task-group.js │ │ │ │ │ ├── task.js │ │ │ │ │ └── taskable.js │ │ │ │ └── yieldables.js │ │ │ ├── helpers.js │ │ │ ├── scheduler │ │ │ │ └── ember-scheduler.js │ │ │ ├── task-decorators.js │ │ │ ├── task-factory.js │ │ │ ├── task-group.js │ │ │ ├── task-instance.js │ │ │ ├── task-properties.js │ │ │ ├── task-public-api.js │ │ │ ├── task.js │ │ │ ├── taskable-mixin.js │ │ │ ├── tracked-state.js │ │ │ ├── utils.js │ │ │ └── wait-for.js │ │ ├── -task-instance.js │ │ ├── -task-property.js │ │ ├── async-arrow-runtime.js │ │ ├── helpers │ │ │ ├── cancel-all.ts │ │ │ ├── perform.ts │ │ │ └── task.ts │ │ ├── index.d.ts │ │ ├── index.js │ │ └── template-registry.ts │ ├── tsconfig.json │ └── unpublished-development-types │ │ └── index.d.ts └── test-app │ ├── .editorconfig │ ├── .ember-cli │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .jsdoc │ ├── .prettierignore │ ├── .prettierrc │ ├── .template-lintrc.js │ ├── .watchmanconfig │ ├── API.md │ ├── README.md │ ├── app │ ├── app.ts │ ├── application │ │ ├── controller.js │ │ ├── route.js │ │ └── template.hbs │ ├── components │ │ ├── ajax-throttling-example │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── caps-marquee │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── code-snippet.gts │ │ ├── code-template-toggle │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── concurrency-graph │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── count-up │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── events-example │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── github-edit │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── loading-spinner │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── my-button.js │ │ ├── nav-header │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── press-and-hold-button │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── scrambled-text │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── shared-tutorial │ │ │ └── component.js │ │ ├── task-function-syntax-1 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── task-function-syntax-2 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── task-function-syntax-3 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── task-function-syntax-4 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── task-lifecycle-events-example │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── tests │ │ │ └── basic-template-imports.gts │ │ ├── tutorial-0 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── tutorial-1 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── tutorial-2 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── tutorial-3 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── tutorial-4 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── tutorial-5 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── tutorial-6 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── tutorial-7 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ ├── tutorial-8 │ │ │ ├── component.js │ │ │ └── template.hbs │ │ └── tutorial-9 │ │ │ ├── component.js │ │ │ └── template.hbs │ ├── docs │ │ ├── 404 │ │ │ └── route.js │ │ ├── advanced │ │ │ ├── encapsulated-task │ │ │ │ ├── controller.js │ │ │ │ └── template.hbs │ │ │ ├── lifecycle-events │ │ │ │ └── template.hbs │ │ │ ├── task-modifiers │ │ │ │ ├── controller.js │ │ │ │ └── template.hbs │ │ │ └── yieldables │ │ │ │ └── template.hbs │ │ ├── cancelation │ │ │ ├── controller.js │ │ │ └── template.hbs │ │ ├── child-tasks │ │ │ ├── controller.js │ │ │ └── template.hbs │ │ ├── controller.js │ │ ├── derived-state │ │ │ ├── controller.js │ │ │ └── template.hbs │ │ ├── encapsulated-task │ │ │ └── route.js │ │ ├── error-vs-cancelation │ │ │ ├── controller.js │ │ │ └── template.hbs │ │ ├── events │ │ │ └── template.hbs │ │ ├── examples │ │ │ ├── ajax-throttling │ │ │ │ └── template.hbs │ │ │ ├── autocomplete │ │ │ │ ├── controller.js │ │ │ │ └── template.hbs │ │ │ ├── increment-buttons │ │ │ │ ├── controller.js │ │ │ │ └── template.hbs │ │ │ ├── index │ │ │ │ └── template.hbs │ │ │ ├── joining-tasks │ │ │ │ ├── controller.js │ │ │ │ └── template.hbs │ │ │ ├── loading-ui │ │ │ │ ├── controller.js │ │ │ │ └── template.hbs │ │ │ ├── route-tasks │ │ │ │ ├── controller.js │ │ │ │ ├── detail │ │ │ │ │ └── route.js │ │ │ │ ├── route.js │ │ │ │ └── template.hbs │ │ │ └── task-concurrency │ │ │ │ └── route.js │ │ ├── faq │ │ │ └── template.hbs │ │ ├── index │ │ │ └── route.js │ │ ├── installation │ │ │ └── template.hbs │ │ ├── introduction │ │ │ └── template.hbs │ │ ├── older-versions │ │ │ └── template.hbs │ │ ├── task-cancelation-help │ │ │ └── template.hbs │ │ ├── task-concurrency-advanced │ │ │ ├── controller.js │ │ │ └── template.hbs │ │ ├── task-concurrency │ │ │ ├── controller.js │ │ │ └── template.hbs │ │ ├── task-decorators │ │ │ └── template.hbs │ │ ├── task-function-syntax │ │ │ └── template.hbs │ │ ├── task-groups │ │ │ ├── controller.js │ │ │ └── template.hbs │ │ ├── task-lifecycle-events │ │ │ └── route.js │ │ ├── template.hbs │ │ ├── testing-debugging │ │ │ └── template.hbs │ │ ├── tutorial │ │ │ ├── discussion │ │ │ │ └── template.hbs │ │ │ ├── index │ │ │ │ └── template.hbs │ │ │ └── refactor │ │ │ │ └── template.hbs │ │ ├── typescript │ │ │ └── template.hbs │ │ ├── v4-upgrade │ │ │ └── template.hbs │ │ └── yieldables │ │ │ └── route.js │ ├── helpers-test │ │ ├── controller.js │ │ └── template.hbs │ ├── helpers │ │ ├── caps-bool.js │ │ ├── color.js │ │ ├── pick-from.js │ │ ├── progress-style.js │ │ ├── scale.js │ │ ├── subtract.js │ │ ├── sum.js │ │ ├── swallow-error.js │ │ └── width.js │ ├── index.html │ ├── index │ │ └── route.js │ ├── modifiers │ │ └── autofocus.js │ ├── router.js │ ├── services │ │ ├── fun.js │ │ └── notifications.js │ ├── styles │ │ ├── app.scss │ │ ├── fontello.css │ │ ├── normalize.scss │ │ └── skeleton.scss │ ├── task-injection-test │ │ ├── controller.js │ │ └── template.hbs │ ├── task-modifiers │ │ └── benchmark.js │ ├── templates │ │ └── components │ │ │ └── my-button.hbs │ ├── testing-ergo │ │ ├── foo-settimeout │ │ │ ├── controller.js │ │ │ ├── route.js │ │ │ └── template.hbs │ │ ├── foo │ │ │ ├── controller.js │ │ │ ├── route.js │ │ │ └── template.hbs │ │ ├── loading │ │ │ └── template.hbs │ │ ├── slow │ │ │ ├── route.js │ │ │ └── template.hbs │ │ └── timer-loop │ │ │ ├── controller.js │ │ │ ├── route.js │ │ │ └── template.hbs │ └── utils.js │ ├── config │ ├── ember-cli-update.json │ ├── ember-try.js │ ├── environment.js │ ├── optional-features.json │ └── targets.js │ ├── ember-cli-build.js │ ├── lib │ └── prember-urls.js │ ├── package.json │ ├── public │ ├── CNAME │ ├── crossdomain.xml │ └── robots.txt │ ├── snippets │ ├── babel-transform-config.js │ ├── ember-install.sh │ ├── encapsulated-task.js │ ├── last-value-decorator.js │ ├── poll-loop-break-1.js │ ├── poll-loop-classic.js │ ├── poll-loop.js │ ├── task-cancelation-example-1.js │ ├── task-cancelation-example-2.js │ ├── task-cancelation-example-3.js │ ├── task-decorators-1.js │ ├── task-decorators-2.js │ ├── task-decorators-3.js │ ├── task-decorators-4.js │ ├── task-decorators-5.js │ ├── task-group-decorators-1.js │ ├── task-group-decorators-2.js │ ├── task-group-decorators-3.js │ ├── ts │ │ ├── basic-example.ts │ │ ├── template-import-example.txt │ │ ├── template-registry-example.ts │ │ └── typing-task.ts │ ├── yieldable-req-idle-cb-task.js │ └── yieldable-req-idle-cb.js │ ├── testem.js │ ├── tests │ ├── acceptance │ │ ├── helpers-test.js │ │ ├── root-test.js │ │ └── task-injection-test.js │ ├── helpers │ │ ├── helpers.js │ │ └── index.js │ ├── index.html │ ├── integration │ │ ├── async-arrow-task-test.js │ │ ├── helpers │ │ │ ├── perform-test.js │ │ │ └── task-action-test.js │ │ ├── no-render-breaking-test.js │ │ ├── template-imports-test.gts │ │ └── tracked-use-test.js │ ├── test-helper.js │ └── unit │ │ ├── cancelable-promise-helpers-test.js │ │ ├── create-mock-task-in-test.js │ │ ├── decorators-test.js │ │ ├── derived-state │ │ └── on-state-test.js │ │ ├── encapsulated-task-test.js │ │ ├── error-handling-test.js │ │ ├── external │ │ └── scheduler-policies │ │ │ ├── drop-policy-test.js │ │ │ ├── enqueued-policy-test.js │ │ │ ├── helpers.js │ │ │ ├── keep-latest-policy-test.js │ │ │ ├── restartable-policy-test.js │ │ │ └── unbounded-policy-test.js │ │ ├── generator-method-test.js │ │ ├── last-value-test.js │ │ ├── self-cancel-loop-test.js │ │ ├── task-events-test.js │ │ ├── task-groups-test.js │ │ ├── task-instance-test.js │ │ ├── task-property-test.js │ │ ├── task-states-test.js │ │ ├── task-test.js │ │ └── wait-for-test.js │ ├── tsconfig.json │ ├── types-tests │ └── ember-concurrency-test.ts │ └── types │ └── global.d.ts ├── pnpm-lock.yaml └── pnpm-workspace.yaml /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.js] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.hbs] 20 | insert_final_newline = false 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [*.css] 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [*.html] 29 | indent_style = space 30 | indent_size = 2 31 | 32 | [*.{diff,md}] 33 | trim_trailing_whitespace = false 34 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | 9 | jobs: 10 | deploy: 11 | name: Deploy 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: wyvox/action@v1 15 | with: 16 | pnpm-args: --frozen-lockfile 17 | 18 | - name: Build docs assets 19 | run: pnpm --filter test-app docs:buildall && find ./packages/test-app/dist 20 | 21 | - name: Deploy 22 | uses: peaceiris/actions-gh-pages@v3 23 | with: 24 | github_token: ${{ secrets.GITHUB_TOKEN }} 25 | publish_dir: ./packages/test-app/dist 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist/ 5 | tmp/ 6 | /packages/test-app/public/api 7 | 8 | # dependencies 9 | node_modules/ 10 | 11 | # misc 12 | .env* 13 | .pnp* 14 | .sass-cache 15 | .eslintcache 16 | connect.lock 17 | coverage/ 18 | libpeerconnection.log 19 | npm-debug.log* 20 | yarn-error.log 21 | testem.log 22 | .vscode 23 | 24 | # ember-try 25 | .node_modules.ember-try/ 26 | bower.json.ember-try 27 | npm-shrinkwrap.json.ember-try 28 | package.json.ember-try 29 | package-lock.json.ember-try 30 | yarn.lock.ember-try 31 | 32 | # broccoli-debug 33 | DEBUG/ 34 | 35 | .DS_Store 36 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.env* 13 | /.eslintcache 14 | /.eslintignore 15 | /.eslintrc.js 16 | /.git/ 17 | /.github/ 18 | /.gitignore 19 | /.prettierignore 20 | /.prettierrc.js 21 | /.template-lintrc.js 22 | /.travis.yml 23 | /.watchmanconfig 24 | /bower.json 25 | /CONTRIBUTING.md 26 | /ember-cli-build.js 27 | /testem.js 28 | /tests/ 29 | /yarn-error.log 30 | /yarn.lock 31 | .gitkeep 32 | 33 | # ember-try 34 | /.node_modules.ember-try/ 35 | /bower.json.ember-try 36 | /npm-shrinkwrap.json.ember-try 37 | /package.json.ember-try 38 | /package-lock.json.ember-try 39 | /yarn.lock.ember-try 40 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | ember-concurrency.com 2 | 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project uses `pnpm` as a package manager. If you're adding a new dependency, ensure that the `pnpm-lock.yaml` lockfile is updated and committed into your pull request. 4 | 5 | ## Addon Maintenance 6 | 7 | ### Installation 8 | 9 | * `git clone` this repository 10 | * `pnpm i` 11 | 12 | ### Running 13 | 14 | * `ember server` 15 | * Visit your app at http://localhost:4200. 16 | 17 | ### Running Tests 18 | 19 | * `pnpm test:ember-compatibility` (Runs `ember try:each` to test your addon against multiple Ember versions) 20 | * `ember test` 21 | * `ember test --server` 22 | 23 | ### Building 24 | 25 | * `ember build` 26 | 27 | For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/). 28 | 29 | ### Releasing new versions / publishing to NPM 30 | 31 | ``` 32 | cd packages/ember-concurrency 33 | pnpm build 34 | npx release-it 35 | ``` 36 | 37 | ### Publishing Guides and API Docs 38 | 39 | The [docs site](https://www.ember-concurrency.com) is built and deployed when merging to master. See [docs.yml](./.github/workflows/deploy-docs.yml). 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Alex Matchneer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "concurrently 'pnpm:start:*' --restart-after 5000 --prefix-colors cyan,white,yellow", 5 | "start:addon": "pnpm --filter ember-concurrency run start", 6 | "start:test-app": "pnpm --filter test-app run start", 7 | "lint": "pnpm run --filter '*' lint", 8 | "lint:fix": "pnpm run --filter '*' lint:fix", 9 | "test": "pnpm --filter test-app run test", 10 | "test:ember": "pnpm --filter test-app run test:ember" 11 | }, 12 | "devDependencies": { 13 | "concurrently": "^8.0.0" 14 | }, 15 | "volta": { 16 | "node": "16.19.0", 17 | "pnpm": "8.15.1" 18 | }, 19 | "version": "4.0.0-beta.2" 20 | } 21 | -------------------------------------------------------------------------------- /packages/ember-concurrency/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /declarations/ 8 | /tmp/ 9 | 10 | # dependencies 11 | /bower_components/ 12 | /node_modules/ 13 | 14 | # misc 15 | /coverage/ 16 | !.* 17 | .*/ 18 | .eslintcache 19 | 20 | # ember-try 21 | /.node_modules.ember-try/ 22 | /bower.json.ember-try 23 | /npm-shrinkwrap.json.ember-try 24 | /package.json.ember-try 25 | /package-lock.json.ember-try 26 | /yarn.lock.ember-try 27 | 28 | # snippets 29 | /packages/test-app/snippets/ 30 | 31 | # jsdoc assets 32 | /packages/test-app/public/ 33 | -------------------------------------------------------------------------------- /packages/ember-concurrency/.gitignore: -------------------------------------------------------------------------------- 1 | # The authoritative copies of these live in the monorepo root (because they're 2 | # more useful on github that way), but the build copies them into here so they 3 | # will also appear in published NPM packages. 4 | /README.md 5 | /LICENSE.md 6 | 7 | # compiled output 8 | /dist 9 | /declarations 10 | 11 | # npm/pnpm/yarn pack output 12 | *.tgz 13 | -------------------------------------------------------------------------------- /packages/ember-concurrency/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | .lint-todo/ 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /packages/ember-concurrency/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | overrides: [ 5 | { 6 | files: '*.{js,ts}', 7 | options: { 8 | singleQuote: true, 9 | }, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/ember-concurrency/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | rules: { 6 | 'no-whitespace-for-layout': false, 7 | 'require-input-label': false, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/ember-concurrency/addon-main.cjs: -------------------------------------------------------------------------------- 1 | const { addonV1Shim } = require("@embroider/addon-shim"); 2 | module.exports = addonV1Shim(__dirname); 3 | -------------------------------------------------------------------------------- /packages/ember-concurrency/babel.config.json: -------------------------------------------------------------------------------- 1 | // Babel config for building the ember-concurrency package itself. Because EC is a V2 addon, 2 | // this babel config should *not* apply presets or compile away ES modules. 3 | // It exists only to provide development niceties for you, like automatic 4 | // template colocation. 5 | { 6 | "plugins": [ 7 | [ 8 | "@babel/plugin-transform-typescript", 9 | { 10 | "allExtensions": true, 11 | "onlyRemoveTypeImports": true, 12 | "allowDeclareFields": true 13 | } 14 | ], 15 | "@embroider/addon-dev/template-colocation-plugin", 16 | [ 17 | "babel-plugin-ember-template-compilation", 18 | { 19 | "targetFormat": "hbs", 20 | "transforms": [] 21 | } 22 | ], 23 | [ 24 | "module:decorator-transforms", 25 | { "runtime": { "import": "decorator-transforms/runtime" } } 26 | ] 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /packages/ember-concurrency/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import babel from "@rollup/plugin-babel"; 2 | import copy from "rollup-plugin-copy"; 3 | import { Addon } from "@embroider/addon-dev/rollup"; 4 | 5 | const addon = new Addon({ 6 | srcDir: "src", 7 | destDir: "dist", 8 | }); 9 | 10 | export default { 11 | // This provides defaults that work well alongside `publicEntrypoints` below. 12 | // You can augment this if you need to. 13 | output: addon.output(), 14 | 15 | plugins: [ 16 | // These are the modules that users should be able to import from your 17 | // addon. Anything not listed here may get optimized away. 18 | addon.publicEntrypoints(["**/*.js", "index.js", "template-registry.js"]), 19 | 20 | // These are the modules that should get reexported into the traditional 21 | // "app" tree. Things in here should also be in publicEntrypoints above, but 22 | // not everything in publicEntrypoints necessarily needs to go here. 23 | addon.appReexports(["helpers/**/*.js"]), 24 | 25 | // Follow the V2 Addon rules about dependencies. Your code can import from 26 | // `dependencies` and `peerDependencies` as well as standard Ember-provided 27 | // package names. 28 | addon.dependencies(), 29 | 30 | // This babel config should *not* apply presets or compile away ES modules. 31 | // It exists only to provide development niceties for you, like automatic 32 | // template colocation. 33 | // 34 | // By default, this will load the actual babel config from the file 35 | // babel.config.json. 36 | babel({ 37 | extensions: [".js", ".gjs", ".ts", ".gts"], 38 | babelHelpers: "bundled", 39 | }), 40 | 41 | // Ensure that standalone .hbs files are properly integrated as Javascript. 42 | addon.hbs(), 43 | 44 | // Remove leftover build artifacts when starting a new build. 45 | addon.clean(), 46 | 47 | // Copy Readme and License into published package 48 | copy({ 49 | targets: [ 50 | { src: "../../README.md", dest: "." }, 51 | { src: "../../LICENSE.md", dest: "." }, 52 | ], 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/async-arrow-runtime.js: -------------------------------------------------------------------------------- 1 | import { TaskFactory } from './task-factory'; 2 | 3 | /** 4 | * This builder function is called by the transpiled code from 5 | * `task(async () => {})`. See async-arrow-task-transform.js 6 | * 7 | * @private 8 | */ 9 | export function buildTask(contextFn, options, taskName, bufferPolicyName) { 10 | let optionsWithBufferPolicy = options; 11 | 12 | if (bufferPolicyName) { 13 | optionsWithBufferPolicy = Object.assign({}, optionsWithBufferPolicy); 14 | optionsWithBufferPolicy[bufferPolicyName] = true; 15 | } 16 | 17 | const result = contextFn(); 18 | 19 | const taskFactory = new TaskFactory( 20 | taskName || '', 21 | result.generator, 22 | optionsWithBufferPolicy, 23 | ); 24 | return taskFactory.createTask(result.context); 25 | } 26 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/ember-environment.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { defer } from 'rsvp'; 3 | import { Environment } from './external/environment'; 4 | import { assert } from '@ember/debug'; 5 | import { join, next, schedule } from '@ember/runloop'; 6 | 7 | export class EmberEnvironment extends Environment { 8 | assert(...args) { 9 | assert(...args); 10 | } 11 | 12 | async(callback) { 13 | join(() => schedule('actions', callback)); 14 | } 15 | 16 | reportUncaughtRejection(error) { 17 | next(null, function () { 18 | if (Ember.onerror) { 19 | Ember.onerror(error); 20 | } else { 21 | throw error; 22 | } 23 | }); 24 | } 25 | 26 | defer() { 27 | return defer(); 28 | } 29 | 30 | globalDebuggingEnabled() { 31 | return Ember.ENV.DEBUG_TASKS; 32 | } 33 | } 34 | 35 | export const EMBER_ENVIRONMENT = new EmberEnvironment(); 36 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/environment.js: -------------------------------------------------------------------------------- 1 | export class Environment { 2 | assert() {} 3 | 4 | async(callback) { 5 | Promise.resolve().then(callback); 6 | } 7 | 8 | reportUncaughtRejection() { 9 | this.async((error) => { 10 | throw error; 11 | }); 12 | } 13 | 14 | defer() { 15 | let deferable = { 16 | promise: null, 17 | resolve: null, 18 | reject: null, 19 | }; 20 | 21 | let promise = new Promise((resolve, reject) => { 22 | deferable.resolve = resolve; 23 | deferable.reject = reject; 24 | }); 25 | 26 | deferable.promise = promise; 27 | 28 | return deferable; 29 | } 30 | 31 | globalDebuggingEnabled() { 32 | return false; 33 | } 34 | } 35 | 36 | export const DEFAULT_ENVIRONMENT = new Environment(); 37 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/generator-state.js: -------------------------------------------------------------------------------- 1 | export class GeneratorStepResult { 2 | constructor(value, done, errored) { 3 | this.value = value; 4 | this.done = done; 5 | this.errored = errored; 6 | } 7 | } 8 | 9 | export class GeneratorState { 10 | constructor(generatorFactory) { 11 | this.done = false; 12 | this.generatorFactory = generatorFactory; 13 | this.iterator = null; 14 | } 15 | 16 | step(resolvedValue, iteratorMethod) { 17 | try { 18 | let iterator = this.getIterator(); 19 | let { value, done } = iterator[iteratorMethod](resolvedValue); 20 | 21 | if (done) { 22 | return this.finalize(value, false); 23 | } else { 24 | return new GeneratorStepResult(value, false, false); 25 | } 26 | } catch (e) { 27 | return this.finalize(e, true); 28 | } 29 | } 30 | 31 | getIterator() { 32 | if (!this.iterator && !this.done) { 33 | this.iterator = this.generatorFactory(); 34 | } 35 | return this.iterator; 36 | } 37 | 38 | finalize(value, errored) { 39 | this.done = true; 40 | this.iterator = null; 41 | return new GeneratorStepResult(value, true, errored); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/policies/bounded-policy.js: -------------------------------------------------------------------------------- 1 | class BoundedPolicy { 2 | constructor(maxConcurrency) { 3 | this.maxConcurrency = maxConcurrency || 1; 4 | } 5 | } 6 | 7 | export default BoundedPolicy; 8 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/policies/drop-policy.js: -------------------------------------------------------------------------------- 1 | import BoundedPolicy from './bounded-policy'; 2 | import { makeCancelState, STARTED } from './execution-states'; 3 | 4 | const CANCELLED = makeCancelState( 5 | `it belongs to a 'drop' Task that was already running`, 6 | ); 7 | 8 | class DropReducer { 9 | constructor(remainingSlots) { 10 | this.remainingSlots = remainingSlots; 11 | } 12 | 13 | step() { 14 | if (this.remainingSlots > 0) { 15 | this.remainingSlots--; 16 | return STARTED; 17 | } 18 | 19 | return CANCELLED; 20 | } 21 | } 22 | 23 | class DropPolicy extends BoundedPolicy { 24 | makeReducer() { 25 | return new DropReducer(this.maxConcurrency); 26 | } 27 | } 28 | 29 | export default DropPolicy; 30 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/policies/enqueued-policy.js: -------------------------------------------------------------------------------- 1 | import BoundedPolicy from './bounded-policy'; 2 | import { STARTED, QUEUED } from './execution-states'; 3 | 4 | class EnqueuedReducer { 5 | constructor(remainingSlots) { 6 | this.remainingSlots = remainingSlots; 7 | } 8 | 9 | step() { 10 | if (this.remainingSlots > 0) { 11 | this.remainingSlots--; 12 | return STARTED; 13 | } else { 14 | return QUEUED; 15 | } 16 | } 17 | } 18 | 19 | class EnqueuedPolicy extends BoundedPolicy { 20 | makeReducer() { 21 | return new EnqueuedReducer(this.maxConcurrency); 22 | } 23 | } 24 | 25 | export default EnqueuedPolicy; 26 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/policies/execution-states.js: -------------------------------------------------------------------------------- 1 | export const TYPE_CANCELLED = 'CANCELLED'; 2 | export const TYPE_STARTED = 'STARTED'; 3 | export const TYPE_QUEUED = 'QUEUED'; 4 | 5 | export const STARTED = { type: TYPE_STARTED }; 6 | export const QUEUED = { type: TYPE_QUEUED }; 7 | 8 | export const makeCancelState = (reason) => ({ type: TYPE_CANCELLED, reason }); 9 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/policies/keep-latest-policy.js: -------------------------------------------------------------------------------- 1 | import BoundedPolicy from './bounded-policy'; 2 | import { STARTED, QUEUED, makeCancelState } from './execution-states'; 3 | 4 | const CANCELLED = makeCancelState( 5 | `it belongs to a 'keepLatest' Task that was already running`, 6 | ); 7 | 8 | // Given: 9 | // - started tasks: [a,b,_] 10 | // - queued tasks: [c,d,e,f] 11 | // KeepLatest will cancel all but the last queued task instance, producing: 12 | // - started tasks: [a,b,c] 13 | // - queued tasks: [f] 14 | 15 | // TODO: perhaps we should expose another config for the number to keep enqueued. 16 | // this would also make sense for enqueued, e.g. perform a max of maxConcurrency 17 | // concurrent task instances, but after a number of queued instances has been 18 | // reached, they should be cancelled. 19 | 20 | class KeepLatestReducer { 21 | constructor(remainingSlots, numToCancel) { 22 | this.remainingSlots = remainingSlots; 23 | this.numToCancel = numToCancel; 24 | } 25 | 26 | step() { 27 | if (this.remainingSlots > 0) { 28 | this.remainingSlots--; 29 | return STARTED; 30 | } else { 31 | if (this.numToCancel > 0) { 32 | this.numToCancel--; 33 | return CANCELLED; 34 | } else { 35 | return QUEUED; 36 | } 37 | } 38 | } 39 | } 40 | 41 | class KeepLatestPolicy extends BoundedPolicy { 42 | makeReducer(numRunning, numQueued) { 43 | let maxEnqueued = 1; 44 | let totalRunning = numRunning + numQueued; 45 | return new KeepLatestReducer( 46 | this.maxConcurrency, 47 | totalRunning - this.maxConcurrency - maxEnqueued, 48 | ); 49 | } 50 | } 51 | 52 | export default KeepLatestPolicy; 53 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/policies/restartable-policy.js: -------------------------------------------------------------------------------- 1 | import BoundedPolicy from './bounded-policy'; 2 | import { STARTED, makeCancelState } from './execution-states'; 3 | 4 | const CANCELLED = makeCancelState( 5 | `it belongs to a 'restartable' Task that was .perform()ed again`, 6 | ); 7 | 8 | class RestartableReducer { 9 | constructor(numToCancel) { 10 | this.numToCancel = numToCancel; 11 | } 12 | 13 | step() { 14 | if (this.numToCancel > 0) { 15 | this.numToCancel--; 16 | return CANCELLED; 17 | } else { 18 | return STARTED; 19 | } 20 | } 21 | } 22 | 23 | class RestartablePolicy extends BoundedPolicy { 24 | makeReducer(numRunning, numQueued) { 25 | return new RestartableReducer(numRunning + numQueued - this.maxConcurrency); 26 | } 27 | } 28 | 29 | export default RestartablePolicy; 30 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/policies/unbounded-policy.js: -------------------------------------------------------------------------------- 1 | import { STARTED } from './execution-states'; 2 | 3 | class UnboundedReducer { 4 | step() { 5 | return STARTED; 6 | } 7 | } 8 | 9 | const SINGLETON_REDUCER = new UnboundedReducer(); 10 | 11 | class UnboundedPolicy { 12 | makeReducer() { 13 | return SINGLETON_REDUCER; 14 | } 15 | } 16 | 17 | export default UnboundedPolicy; 18 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/scheduler.js: -------------------------------------------------------------------------------- 1 | import SchedulerRefresh from './refresh'; 2 | import StateTracker from './state-tracker/state-tracker'; 3 | import NullStateTracker from './state-tracker/null-state-tracker'; 4 | 5 | // Scheduler base class 6 | 7 | // When a Task is performed, it creates an unstarted TaskInstance and 8 | // passes it to the Scheduler to determine when it should run. The 9 | // scheduler consults the schedulerPolicy (e.g. DropPolicy, RestartablePolicy, etc) 10 | // to determine whether the task instance should start executing, be enqueued 11 | // for later execution, or be immediately cancelled. As TaskInstances start 12 | // and run to completion, the Scheduler's `refresh()` method is called to 13 | // give it an opportunity to start (or cancel) previously enqueued task instances, 14 | // as well as update the derived state on Tasks and TaskGroups. 15 | 16 | // Every Task has its own Scheduler instance, unless it is part of a group, 17 | // in which case all the Tasks in a group share a single Scheduler. 18 | 19 | class Scheduler { 20 | constructor(schedulerPolicy, stateTrackingEnabled) { 21 | this.schedulerPolicy = schedulerPolicy; 22 | this.stateTrackingEnabled = stateTrackingEnabled; 23 | this.taskInstances = []; 24 | } 25 | 26 | cancelAll(guid, cancelRequest) { 27 | let cancelations = this.taskInstances 28 | .map((taskInstance) => { 29 | if (taskInstance.task.guids[guid]) { 30 | taskInstance.executor.cancel(cancelRequest); 31 | } 32 | }) 33 | .filter((cancelation) => !!cancelation); 34 | 35 | return Promise.all(cancelations); 36 | } 37 | 38 | perform(taskInstance) { 39 | taskInstance.onFinalize(() => this.scheduleRefresh()); 40 | this.taskInstances.push(taskInstance); 41 | this.refresh(); 42 | } 43 | 44 | scheduleRefresh() { 45 | Promise.resolve().then(() => this.refresh()); 46 | } 47 | 48 | refresh() { 49 | let stateTracker = this.stateTrackingEnabled 50 | ? new StateTracker() 51 | : new NullStateTracker(); 52 | let refresh = new SchedulerRefresh( 53 | this.schedulerPolicy, 54 | stateTracker, 55 | this.taskInstances, 56 | ); 57 | this.taskInstances = refresh.process(); 58 | } 59 | } 60 | 61 | export default Scheduler; 62 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/state-tracker/null-state-tracker.js: -------------------------------------------------------------------------------- 1 | import NullState from './null-state'; 2 | 3 | const NULL_STATE = new NullState(); 4 | 5 | class NullStateTracker { 6 | stateFor() { 7 | return NULL_STATE; 8 | } 9 | 10 | computeFinalStates() {} 11 | } 12 | 13 | export default NullStateTracker; 14 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/state-tracker/null-state.js: -------------------------------------------------------------------------------- 1 | class NullState { 2 | onCompletion() {} 3 | onPerformed() {} 4 | onStart() {} 5 | onRunning() {} 6 | onQueued() {} 7 | } 8 | 9 | export default NullState; 10 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/state-tracker/state-tracker.js: -------------------------------------------------------------------------------- 1 | import RefreshState from './state'; 2 | 3 | const CURRENT_REFRESH_TAGS = new Map(); 4 | 5 | class StateTracker { 6 | constructor() { 7 | this.states = new Map(); 8 | } 9 | 10 | stateFor(taskable) { 11 | let guid = taskable.guid; 12 | let taskState = this.states.get(guid); 13 | if (!taskState) { 14 | let currentTag = CURRENT_REFRESH_TAGS.has(guid) 15 | ? CURRENT_REFRESH_TAGS.get(guid) 16 | : 0; 17 | taskState = new RefreshState(taskable, ++currentTag); 18 | this.states.set(guid, taskState); 19 | CURRENT_REFRESH_TAGS.set(guid, currentTag); 20 | } 21 | return taskState; 22 | } 23 | 24 | // After cancelling/queueing task instances, we have to recompute the derived state 25 | // of all the tasks that had/have task instances in this scheduler. We do this by 26 | // looping through all the Tasks that we've accumulated state for, and then recursively 27 | // applying/adding to the state of any TaskGroups they belong to. 28 | computeFinalStates(callback) { 29 | this.computeRecursiveState(); 30 | this.forEachState((state) => callback(state)); 31 | } 32 | 33 | computeRecursiveState() { 34 | this.forEachState((taskState) => { 35 | let lastState = taskState; 36 | taskState.recurseTaskGroups((taskGroup) => { 37 | let state = this.stateFor(taskGroup); 38 | state.applyStateFrom(lastState); 39 | lastState = state; 40 | }); 41 | }); 42 | } 43 | 44 | forEachState(callback) { 45 | this.states.forEach((state) => callback(state)); 46 | } 47 | } 48 | 49 | export default StateTracker; 50 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/scheduler/state-tracker/state.js: -------------------------------------------------------------------------------- 1 | import { 2 | COMPLETION_SUCCESS, 3 | COMPLETION_ERROR, 4 | COMPLETION_CANCEL, 5 | } from '../../task-instance/completion-states'; 6 | 7 | class RefreshState { 8 | constructor(taskable, tag) { 9 | this.taskable = taskable; 10 | this.group = taskable.group; 11 | this.numRunning = 0; 12 | this.numQueued = 0; 13 | this.numPerformedInc = 0; 14 | this.attrs = {}; 15 | this.tag = tag; 16 | } 17 | 18 | onCompletion(taskInstance) { 19 | let state = taskInstance.completionState; 20 | this.attrs.lastRunning = null; 21 | this.attrs.lastComplete = taskInstance; 22 | if (state === COMPLETION_SUCCESS) { 23 | this.attrs.lastSuccessful = taskInstance; 24 | } else { 25 | if (state === COMPLETION_ERROR) { 26 | this.attrs.lastErrored = taskInstance; 27 | } else if (state === COMPLETION_CANCEL) { 28 | this.attrs.lastCanceled = taskInstance; 29 | } 30 | this.attrs.lastIncomplete = taskInstance; 31 | } 32 | } 33 | 34 | onPerformed(taskInstance) { 35 | this.numPerformedInc += 1; 36 | this.attrs.lastPerformed = taskInstance; 37 | } 38 | 39 | onStart(taskInstance) { 40 | this.attrs.last = taskInstance; 41 | } 42 | 43 | onRunning(taskInstance) { 44 | this.attrs.lastRunning = taskInstance; 45 | this.numRunning += 1; 46 | } 47 | 48 | onQueued() { 49 | this.numQueued += 1; 50 | } 51 | 52 | recurseTaskGroups(callback) { 53 | let group = this.group; 54 | while (group) { 55 | callback(group); 56 | group = group.group; 57 | } 58 | } 59 | 60 | applyStateFrom(other) { 61 | Object.assign(this.attrs, other.attrs); 62 | this.numRunning += other.numRunning; 63 | this.numQueued += other.numQueued; 64 | this.numPerformedInc += other.numPerformedInc; 65 | } 66 | } 67 | 68 | export default RefreshState; 69 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/task-instance/base.js: -------------------------------------------------------------------------------- 1 | import { INITIAL_STATE } from './initial-state'; 2 | import { yieldableSymbol } from '../yieldables'; 3 | import { CancelRequest, CANCEL_KIND_EXPLICIT } from './cancelation'; 4 | const EXPLICIT_CANCEL_REASON = '.cancel() was explicitly called'; 5 | 6 | export class BaseTaskInstance { 7 | constructor({ task, args, executor, performType, hasEnabledEvents }) { 8 | this.task = task; 9 | this.args = args; 10 | this.performType = performType; 11 | this.executor = executor; 12 | this.executor.taskInstance = this; 13 | this.hasEnabledEvents = hasEnabledEvents; 14 | } 15 | 16 | setState() {} 17 | onStarted() {} 18 | onSuccess() {} 19 | onError() {} 20 | onCancel() {} 21 | formatCancelReason() {} 22 | selfCancelLoopWarning() {} 23 | 24 | onFinalize(callback) { 25 | this.executor.onFinalize(callback); 26 | } 27 | 28 | proceed(index, yieldResumeType, value) { 29 | this.executor.proceedChecked(index, yieldResumeType, value); 30 | } 31 | 32 | [yieldableSymbol](parentTaskInstance, resumeIndex) { 33 | return this.executor.onYielded(parentTaskInstance, resumeIndex); 34 | } 35 | 36 | cancel(cancelReason = EXPLICIT_CANCEL_REASON) { 37 | this.executor.cancel(new CancelRequest(CANCEL_KIND_EXPLICIT, cancelReason)); 38 | } 39 | 40 | then(...args) { 41 | return this.executor.promise().then(...args); 42 | } 43 | 44 | catch(...args) { 45 | return this.executor.promise().catch(...args); 46 | } 47 | 48 | finally(...args) { 49 | return this.executor.promise().finally(...args); 50 | } 51 | 52 | toString() { 53 | return `${this.task} TaskInstance`; 54 | } 55 | 56 | start() { 57 | this.executor.start(); 58 | return this; 59 | } 60 | } 61 | 62 | Object.assign(BaseTaskInstance.prototype, INITIAL_STATE); 63 | Object.assign(BaseTaskInstance.prototype, { 64 | state: 'waiting', 65 | isDropped: false, 66 | isRunning: true, 67 | }); 68 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/task-instance/cancelation.js: -------------------------------------------------------------------------------- 1 | export const TASK_CANCELATION_NAME = 'TaskCancelation'; 2 | 3 | /** 4 | * Returns true if the object passed to it is a TaskCancelation error. 5 | * If you call `someTask.perform().catch(...)` or otherwise treat 6 | * a {@linkcode TaskInstance} like a promise, you may need to 7 | * handle the cancelation of a TaskInstance differently from 8 | * other kinds of errors it might throw, and you can use this 9 | * convenience function to distinguish cancelation from errors. 10 | * 11 | * ```js 12 | * click() { 13 | * this.myTask.perform().catch(e => { 14 | * if (!didCancel(e)) { throw e; } 15 | * }); 16 | * } 17 | * ``` 18 | * 19 | * @param {object} error the caught error, which might be a TaskCancelation 20 | * @returns {boolean} 21 | */ 22 | export function didCancel(e) { 23 | return e && e.name === TASK_CANCELATION_NAME; 24 | } 25 | 26 | export const CANCEL_KIND_EXPLICIT = 'explicit'; 27 | export const CANCEL_KIND_YIELDABLE_CANCEL = 'yielded'; 28 | export const CANCEL_KIND_LIFESPAN_END = 'lifespan_end'; 29 | export const CANCEL_KIND_PARENT_CANCEL = 'parent_cancel'; 30 | 31 | export class CancelRequest { 32 | constructor(kind, reason) { 33 | this.kind = kind; 34 | this.reason = reason; 35 | this.promise = new Promise((resolve) => { 36 | this.finalize = resolve; 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/task-instance/completion-states.js: -------------------------------------------------------------------------------- 1 | export const COMPLETION_PENDING = 0; 2 | export const COMPLETION_SUCCESS = 1; 3 | export const COMPLETION_ERROR = 2; 4 | export const COMPLETION_CANCEL = 3; 5 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/task-instance/initial-state.js: -------------------------------------------------------------------------------- 1 | import { COMPLETION_PENDING } from './completion-states'; 2 | 3 | export const INITIAL_STATE = { 4 | completionState: COMPLETION_PENDING, 5 | 6 | /** 7 | * If this TaskInstance runs to completion by returning a property 8 | * other than a rejecting promise, this property will be set 9 | * with that value. 10 | * 11 | * @memberof TaskInstance 12 | * @instance 13 | * @readOnly 14 | */ 15 | value: null, 16 | 17 | /** 18 | * If this TaskInstance is canceled or throws an error (or yields 19 | * a promise that rejects), this property will be set with that error. 20 | * Otherwise, it is null. 21 | * 22 | * @memberof TaskInstance 23 | * @instance 24 | * @readOnly 25 | */ 26 | error: null, 27 | 28 | /** 29 | * True if the task instance is fulfilled. 30 | * 31 | * @memberof TaskInstance 32 | * @instance 33 | * @readOnly 34 | */ 35 | isSuccessful: false, 36 | 37 | /** 38 | * True if the task instance resolves to a rejection. 39 | * 40 | * @memberof TaskInstance 41 | * @instance 42 | * @readOnly 43 | */ 44 | isError: false, 45 | 46 | /** 47 | * True if the task instance is canceled 48 | * 49 | * @memberof TaskInstance 50 | * @instance 51 | * @readOnly 52 | */ 53 | isCanceled: false, 54 | 55 | /** 56 | * True if the task instance has started, else false. 57 | * 58 | * @memberof TaskInstance 59 | * @instance 60 | * @readOnly 61 | */ 62 | hasStarted: false, 63 | 64 | /** 65 | * True if the task has run to completion. 66 | * 67 | * @memberof TaskInstance 68 | * @instance 69 | * @readOnly 70 | */ 71 | isFinished: false, 72 | }; 73 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/task/default-state.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_STATE = { 2 | last: null, 3 | lastRunning: null, 4 | lastPerformed: null, 5 | lastSuccessful: null, 6 | lastComplete: null, 7 | lastErrored: null, 8 | lastCanceled: null, 9 | lastIncomplete: null, 10 | performCount: 0, 11 | }; 12 | 13 | Object.freeze(DEFAULT_STATE); 14 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/task/task-group.js: -------------------------------------------------------------------------------- 1 | import { Taskable } from './taskable'; 2 | 3 | export class TaskGroup extends Taskable {} 4 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/external/task/taskable.js: -------------------------------------------------------------------------------- 1 | import { DEFAULT_STATE } from './default-state'; 2 | import { 3 | CancelRequest, 4 | CANCEL_KIND_EXPLICIT, 5 | } from '../task-instance/cancelation'; 6 | 7 | let guidId = 0; 8 | function makeGuid() { 9 | return `ec_${guidId++}`; 10 | } 11 | 12 | export class Taskable { 13 | constructor(options) { 14 | this.context = options.context; 15 | this.debug = options.debug || false; 16 | this.enabledModifiers = options.enabledModifiers; 17 | this.env = options.env; 18 | this.group = options.group; 19 | this.hasEnabledEvents = options.hasEnabledEvents; 20 | this.modifierOptions = options.modifierOptions; 21 | this.name = options.name; 22 | this.onStateCallback = options.onStateCallback; 23 | this.scheduler = options.scheduler; 24 | 25 | this.guid = makeGuid(); 26 | this.guids = {}; 27 | this.guids[this.guid] = true; 28 | if (this.group) { 29 | Object.assign(this.guids, this.group.guids); 30 | } 31 | } 32 | 33 | cancelAll(options) { 34 | let { reason, cancelRequestKind, resetState } = options || {}; 35 | reason = reason || '.cancelAll() was explicitly called on the Task'; 36 | 37 | let cancelRequest = new CancelRequest( 38 | cancelRequestKind || CANCEL_KIND_EXPLICIT, 39 | reason, 40 | ); 41 | return this.scheduler.cancelAll(this.guid, cancelRequest).then(() => { 42 | if (resetState) { 43 | this._resetState(); 44 | } 45 | }); 46 | } 47 | 48 | get _isAlive() { 49 | return true; 50 | } 51 | 52 | _resetState() { 53 | this.setState(DEFAULT_STATE); 54 | } 55 | 56 | // override 57 | setState() {} 58 | } 59 | 60 | Object.assign(Taskable.prototype, DEFAULT_STATE); 61 | Object.assign(Taskable.prototype, { 62 | numRunning: 0, 63 | numQueued: 0, 64 | isRunning: false, 65 | isQueued: false, 66 | isIdle: true, 67 | state: 'idle', 68 | }); 69 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/helpers.js: -------------------------------------------------------------------------------- 1 | import { get } from '@ember/object'; 2 | import { assert } from '@ember/debug'; 3 | 4 | export function taskHelperClosure(helperName, taskMethod, _args, hash) { 5 | let task = _args[0]; 6 | let outerArgs = _args.slice(1); 7 | 8 | return function (...innerArgs) { 9 | if (!task || typeof task[taskMethod] !== 'function') { 10 | assert( 11 | `The first argument passed to the \`${helperName}\` helper should be a Task object (without quotes); you passed ${task}`, 12 | false, 13 | ); 14 | return; 15 | } 16 | 17 | if (hash && hash.value) { 18 | let event = innerArgs.pop(); 19 | innerArgs.push(get(event, hash.value)); 20 | } 21 | 22 | return task[taskMethod](...outerArgs, ...innerArgs); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/scheduler/ember-scheduler.js: -------------------------------------------------------------------------------- 1 | import Scheduler from '../external/scheduler/scheduler'; 2 | import { once } from '@ember/runloop'; 3 | 4 | class EmberScheduler extends Scheduler { 5 | scheduleRefresh() { 6 | once(this, this.refresh); 7 | } 8 | } 9 | 10 | export default EmberScheduler; 11 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/taskable-mixin.js: -------------------------------------------------------------------------------- 1 | export const TASKABLE_MIXIN = { 2 | _performCount: 0, 3 | 4 | setState(state) { 5 | this._performCount = this._performCount + (state.numPerformedInc || 0); 6 | 7 | let isRunning = state.numRunning > 0; 8 | let isQueued = state.numQueued > 0; 9 | let derivedState = Object.assign({}, state, { 10 | performCount: this._performCount, 11 | isRunning, 12 | isQueued, 13 | isIdle: !isRunning && !isQueued, 14 | state: isRunning ? 'running' : 'idle', 15 | }); 16 | Object.assign(this, derivedState); 17 | }, 18 | 19 | onState(state, task) { 20 | if (task.onStateCallback) { 21 | task.onStateCallback(state, task); 22 | } 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/tracked-state.js: -------------------------------------------------------------------------------- 1 | import { tracked } from '@glimmer/tracking'; 2 | import { DEFAULT_STATE as INITIAL_TASK_STATE } from './external/task/default-state'; 3 | import { INITIAL_STATE as INITIAL_INSTANCE_STATE } from './external/task-instance/initial-state'; 4 | 5 | function trackMixin(proto, obj, key) { 6 | const propDesc = Object.getOwnPropertyDescriptor(proto, key); 7 | propDesc.initializer = propDesc.initializer || (() => proto[key]); 8 | delete propDesc.value; 9 | const desc = tracked(obj, key, propDesc); 10 | obj[key] = desc; 11 | return obj; 12 | } 13 | 14 | function applyTracked(proto, initial) { 15 | return Object.keys(proto).reduce((acc, key) => { 16 | return trackMixin(proto, acc, key); 17 | }, initial); 18 | } 19 | 20 | export let TRACKED_INITIAL_TASK_STATE; 21 | export let TRACKED_INITIAL_INSTANCE_STATE; 22 | 23 | TRACKED_INITIAL_TASK_STATE = applyTracked(INITIAL_TASK_STATE, {}); 24 | TRACKED_INITIAL_TASK_STATE = applyTracked( 25 | { 26 | numRunning: 0, 27 | numQueued: 0, 28 | isRunning: false, 29 | isQueued: false, 30 | isIdle: true, 31 | state: 'idle', 32 | }, 33 | TRACKED_INITIAL_TASK_STATE, 34 | ); 35 | 36 | TRACKED_INITIAL_INSTANCE_STATE = applyTracked(INITIAL_INSTANCE_STATE, {}); 37 | TRACKED_INITIAL_INSTANCE_STATE = applyTracked( 38 | { 39 | state: 'waiting', 40 | isDropped: false, 41 | isRunning: false, 42 | }, 43 | TRACKED_INITIAL_INSTANCE_STATE, 44 | ); 45 | 46 | Object.freeze(TRACKED_INITIAL_TASK_STATE); 47 | Object.freeze(TRACKED_INITIAL_INSTANCE_STATE); 48 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-private/utils.js: -------------------------------------------------------------------------------- 1 | import { later, cancel } from '@ember/runloop'; 2 | import { EMBER_ENVIRONMENT } from './ember-environment'; 3 | import { Yieldable } from './external/yieldables'; 4 | 5 | export function isEventedObject(c) { 6 | return ( 7 | c && 8 | ((typeof c.one === 'function' && typeof c.off === 'function') || 9 | (typeof c.on === 'function' && typeof c.off === 'function') || 10 | (typeof c.addEventListener === 'function' && 11 | typeof c.removeEventListener === 'function')) 12 | ); 13 | } 14 | 15 | export class EmberYieldable extends Yieldable { 16 | _deferable() { 17 | return EMBER_ENVIRONMENT.defer(); 18 | } 19 | } 20 | 21 | class TimeoutYieldable extends EmberYieldable { 22 | constructor(ms) { 23 | super(); 24 | this.ms = ms; 25 | } 26 | 27 | onYield(state) { 28 | let timerId = later(() => state.next(), this.ms); 29 | 30 | return () => cancel(timerId); 31 | } 32 | } 33 | 34 | /** 35 | * 36 | * Yielding `timeout(ms)` will pause a task for the duration 37 | * of time passed in, in milliseconds. 38 | * 39 | * This timeout will be scheduled on the Ember runloop, which 40 | * means that test helpers will wait for it to complete before 41 | * continuing with the test. See `rawTimeout()` if you need 42 | * different behavior. 43 | * 44 | * The task below, when performed, will print a message to the 45 | * console every second. 46 | * 47 | * ```js 48 | * export default class MyComponent extends Component { 49 | * @task *myTask() { 50 | * while (true) { 51 | * console.log("Hello!"); 52 | * yield timeout(1000); 53 | * } 54 | * } 55 | * } 56 | * ``` 57 | * 58 | * @param {number} ms - the amount of time to sleep before resuming 59 | * the task, in milliseconds 60 | */ 61 | export function timeout(ms) { 62 | return new TimeoutYieldable(ms); 63 | } 64 | 65 | export function deprecatePrivateModule(moduleName) { 66 | // eslint-disable-next-line no-console 67 | console.warn( 68 | `an Ember addon is importing a private ember-concurrency module '${moduleName}' that has moved`, 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-task-instance.js: -------------------------------------------------------------------------------- 1 | import { TaskInstance } from './-private/task-instance'; 2 | import { deprecatePrivateModule } from './-private/utils'; 3 | deprecatePrivateModule('ember-concurrency/-task-instance'); 4 | export default TaskInstance; 5 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/-task-property.js: -------------------------------------------------------------------------------- 1 | import { Task } from './-private/task'; 2 | import { TaskProperty } from './-private/task-properties'; 3 | import { deprecatePrivateModule } from './-private/utils'; 4 | deprecatePrivateModule('ember-concurrency/-task-property'); 5 | export { Task, TaskProperty }; 6 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/async-arrow-runtime.js: -------------------------------------------------------------------------------- 1 | export { buildTask } from './-private/async-arrow-runtime'; 2 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/helpers/cancel-all.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { assert } from '@ember/debug'; 3 | import { taskHelperClosure } from '../-private/helpers'; 4 | import type { Task } from '../index'; 5 | 6 | const CANCEL_REASON = "the 'cancel-all' template helper was invoked"; 7 | 8 | type CancelAllParams = [task: Task]; 9 | 10 | export function cancelHelper(args: CancelAllParams) { 11 | let cancelable = args[0]; 12 | if (!cancelable || typeof cancelable.cancelAll !== 'function') { 13 | assert( 14 | `The first argument passed to the \`cancel-all\` helper should be a Task or TaskGroup (without quotes); you passed ${cancelable}`, 15 | false, 16 | ); 17 | } 18 | 19 | return taskHelperClosure('cancel-all', 'cancelAll', [ 20 | cancelable, 21 | { reason: CANCEL_REASON }, 22 | ]); 23 | } 24 | 25 | export default helper(cancelHelper); 26 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/helpers/perform.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { assert } from '@ember/debug'; 3 | import { taskHelperClosure } from '../-private/helpers'; 4 | import type { Task } from '../index'; 5 | 6 | function maybeReportError( 7 | onError: (error: unknown) => void | null | undefined, 8 | ) { 9 | return function (e: unknown) { 10 | if (typeof onError === 'function') { 11 | onError(e); 12 | } else if (onError === null) { 13 | // Do nothing 14 | } else { 15 | assert( 16 | `The onError argument passed to the \`perform\` helper should be a function or null; you passed ${onError}`, 17 | false, 18 | ); 19 | } 20 | }; 21 | } 22 | 23 | type PerformParams = [task: Task, ...args: any[]]; 24 | 25 | export function performHelper(args: PerformParams, hash: any) { 26 | let perform = taskHelperClosure('perform', 'perform', args, hash); 27 | 28 | if (hash && typeof hash.onError !== 'undefined') { 29 | return function (...innerArgs: any[]) { 30 | try { 31 | let taskInstance = perform(...innerArgs); 32 | return taskInstance.catch(maybeReportError(hash.onError)); 33 | // eslint-disable-next-line no-empty 34 | } catch { 35 | maybeReportError(hash.onError); 36 | } 37 | }; 38 | } else { 39 | return perform; 40 | } 41 | } 42 | 43 | export default helper(performHelper); 44 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/helpers/task.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import type { Task } from '../index'; 3 | 4 | type TaskParams = [task: Task, ...args: any[]]; 5 | 6 | function taskHelper(positional: TaskParams) { 7 | let [task, ...args] = positional; 8 | 9 | // @ts-expect-errors _curry isn't typed yet 10 | return task._curry(...args); 11 | } 12 | 13 | export default helper(taskHelper); 14 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/index.js: -------------------------------------------------------------------------------- 1 | import { timeout, EmberYieldable as Yieldable } from './-private/utils'; 2 | import { TaskProperty, TaskGroupProperty } from './-private/task-properties'; 3 | import { task, taskGroup } from './-private/task-public-api'; 4 | import { TaskInstance } from './-private/task-instance'; 5 | import { 6 | all, 7 | allSettled, 8 | hash, 9 | hashSettled, 10 | race, 11 | } from './-private/cancelable-promise-helpers'; 12 | import { 13 | waitForQueue, 14 | waitForEvent, 15 | waitForProperty, 16 | } from './-private/wait-for'; 17 | import { didCancel } from './-private/external/task-instance/cancelation'; 18 | import { 19 | animationFrame, 20 | forever, 21 | rawTimeout, 22 | } from './-private/external/yieldables'; 23 | import { Task } from './-private/task'; 24 | import { TaskGroup } from './-private/task-group'; 25 | import { 26 | dropTask, 27 | dropTaskGroup, 28 | enqueueTask, 29 | enqueueTaskGroup, 30 | lastValue, 31 | keepLatestTask, 32 | keepLatestTaskGroup, 33 | restartableTask, 34 | restartableTaskGroup, 35 | } from './-private/task-decorators'; 36 | import { 37 | registerModifier, 38 | getModifier, 39 | hasModifier, 40 | } from './-private/external/task-factory'; 41 | 42 | export { 43 | all, 44 | allSettled, 45 | animationFrame, 46 | didCancel, 47 | dropTask, 48 | dropTaskGroup, 49 | enqueueTask, 50 | enqueueTaskGroup, 51 | forever, 52 | getModifier, 53 | hash, 54 | hashSettled, 55 | hasModifier, 56 | keepLatestTask, 57 | keepLatestTaskGroup, 58 | lastValue, 59 | race, 60 | rawTimeout, 61 | registerModifier, 62 | restartableTask, 63 | restartableTaskGroup, 64 | task, 65 | taskGroup, 66 | timeout, 67 | waitForQueue, 68 | waitForEvent, 69 | waitForProperty, 70 | Task, 71 | TaskProperty, 72 | TaskInstance, 73 | TaskGroup, 74 | TaskGroupProperty, 75 | Yieldable, 76 | }; 77 | -------------------------------------------------------------------------------- /packages/ember-concurrency/src/template-registry.ts: -------------------------------------------------------------------------------- 1 | import type cancelAll from './helpers/cancel-all'; 2 | import type perform from './helpers/perform'; 3 | import type task from './helpers/task'; 4 | 5 | export default interface Registry { 6 | perform: typeof perform; 7 | 'cancel-all': typeof cancelAll; 8 | task: typeof task; 9 | } 10 | -------------------------------------------------------------------------------- /packages/ember-concurrency/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember/tsconfig.json", 3 | "include": ["src/**/*", "unpublished-development-types/**/*"], 4 | "glint": { 5 | "environment": ["ember-loose", "ember-template-imports"] 6 | }, 7 | "compilerOptions": { 8 | "allowJs": true, 9 | "declarationDir": "declarations", 10 | 11 | /** 12 | https://www.typescriptlang.org/tsconfig#rootDir 13 | "Default: The longest common path of all non-declaration input files." 14 | 15 | Because we want our declarations' structure to match our rollup output, 16 | we need this "rootDir" to match the "srcDir" in the rollup.config.mjs. 17 | 18 | This way, we can have simpler `package.json#exports` that matches 19 | imports to files on disk 20 | */ 21 | "rootDir": "./src", 22 | 23 | /** 24 | https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax 25 | 26 | We don't want to include types dependencies in our compiled output, so tell TypeScript 27 | to enforce using `import type` instead of `import` for Types. 28 | */ 29 | "verbatimModuleSyntax": true, 30 | 31 | /** 32 | https://www.typescriptlang.org/tsconfig#allowImportingTsExtensions 33 | 34 | We want our tooling to know how to resolve our custom files so the appropriate plugins 35 | can do the proper transformations on those files. 36 | */ 37 | "allowImportingTsExtensions": true 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/ember-concurrency/unpublished-development-types/index.d.ts: -------------------------------------------------------------------------------- 1 | // Add any types here that you need for local development only. 2 | // These will *not* be published as part of your addon, so be careful that your published code does not rely on them! 3 | import 'ember-source/types'; 4 | import 'ember-source/types/preview'; 5 | import '@glint/environment-ember-loose'; 6 | import '@glint/environment-ember-template-imports'; 7 | 8 | declare module '@glint/environment-ember-loose/registry' { 9 | // Remove this once entries have been added! 👇 10 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 11 | export default interface Registry { 12 | // Add any registry entries from other addons here that your addon itself uses (in non-strict mode templates) 13 | // See https://typed-ember.gitbook.io/glint/using-glint/ember/using-addons 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/test-app/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /packages/test-app/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false, 9 | 10 | /** 11 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript 12 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available. 13 | */ 14 | "isTypeScriptProject": false 15 | } 16 | -------------------------------------------------------------------------------- /packages/test-app/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | 27 | public/api 28 | 29 | snippets 30 | 31 | app 32 | -------------------------------------------------------------------------------- /packages/test-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | ecmaVersion: 2018, 8 | sourceType: 'module', 9 | requireConfigFile: false, 10 | ecmaFeatures: { 11 | legacyDecorators: true, 12 | }, 13 | }, 14 | plugins: ['ember'], 15 | extends: [ 16 | 'eslint:recommended', 17 | 'plugin:ember/recommended', 18 | 19 | // This invokes "eslint-config-prettier" to disable ESLint rules that conflict with Prettier. 20 | // Not to be confused with "eslint-plugin-prettier" which runs Prettier as an ESLint rule 21 | // (which we no longer use) 22 | 'prettier', 23 | ], 24 | env: { 25 | browser: true, 26 | }, 27 | rules: { 28 | 'require-yield': 0, 29 | 'ember/no-classic-components': 0, 30 | 'ember/no-classic-classes': 0, 31 | 'ember/no-computed-properties-in-native-classes': 0, 32 | }, 33 | overrides: [ 34 | // node files 35 | { 36 | files: [ 37 | './.eslintrc.js', 38 | './.prettierrc.js', 39 | './.template-lintrc.js', 40 | './ember-cli-build.js', 41 | './testem.js', 42 | './blueprints/*/index.js', 43 | './config/**/*.js', 44 | './lib/*/index.js', 45 | './server/**/*.js', 46 | ], 47 | parserOptions: { 48 | sourceType: 'script', 49 | }, 50 | env: { 51 | browser: false, 52 | node: true, 53 | }, 54 | extends: ['plugin:n/recommended'], 55 | }, 56 | { 57 | // test files 58 | files: ['tests/**/*-test.{js,ts}'], 59 | extends: ['plugin:qunit/recommended'], 60 | }, 61 | ], 62 | }; 63 | -------------------------------------------------------------------------------- /packages/test-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /.eslintcache 16 | /connect.lock 17 | /coverage/ 18 | /libpeerconnection.log 19 | /npm-debug.log* 20 | /testem.log 21 | /yarn-error.log 22 | 23 | # ember-try 24 | /.node_modules.ember-try/ 25 | /bower.json.ember-try 26 | /npm-shrinkwrap.json.ember-try 27 | /package.json.ember-try 28 | /package-lock.json.ember-try 29 | /yarn.lock.ember-try 30 | 31 | # broccoli-debug 32 | /DEBUG/ 33 | -------------------------------------------------------------------------------- /packages/test-app/.jsdoc: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "destination": "public/api/", 4 | "readme": "API.md", 5 | "recurse": true 6 | }, 7 | "plugins": ["plugins/markdown"], 8 | "source": { 9 | "include": ["../ember-concurrency/src"] 10 | }, 11 | "sourceType": "module" 12 | } 13 | -------------------------------------------------------------------------------- /packages/test-app/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | .lint-todo/ 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | 27 | snippets 28 | 29 | public/api 30 | 31 | # Templates are covered by ember template lint's embedded prettier 32 | *.hbs 33 | -------------------------------------------------------------------------------- /packages/test-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-ember-template-tag"], 3 | "overrides": [ 4 | { 5 | "files": "*.{js,ts}", 6 | "options": { 7 | "singleQuote": true 8 | } 9 | }, 10 | { 11 | "files": "*.gjs", 12 | "options": { 13 | "parser": "ember-template-tag", 14 | "singleQuote": true 15 | } 16 | }, 17 | { 18 | "files": "*.gts", 19 | "options": { 20 | "parser": "ember-template-tag", 21 | "singleQuote": true 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /packages/test-app/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: ['recommended'], 5 | rules: { 6 | 'no-whitespace-for-layout': false, 7 | 'require-input-label': false, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/test-app/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/test-app/README.md: -------------------------------------------------------------------------------- 1 | # test-app 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | - [Git](https://git-scm.com/) 11 | - [Node.js](https://nodejs.org/) (with npm) 12 | - [Ember CLI](https://cli.emberjs.com/release/) 13 | - [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | - `git clone ` this repository 18 | - `cd test-app` 19 | - `npm install` 20 | 21 | ## Running / Development 22 | 23 | - `ember serve` 24 | - Visit your app at [http://localhost:4200](http://localhost:4200). 25 | - Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | - `ember test` 34 | - `ember test --server` 35 | 36 | ### Linting 37 | 38 | - `npm run lint` 39 | - `npm run lint:fix` 40 | 41 | ### Building 42 | 43 | - `ember build` (development) 44 | - `ember build --environment production` (production) 45 | 46 | ### Deploying 47 | 48 | Specify what it takes to deploy your app. 49 | 50 | ## Further Reading / Useful Links 51 | 52 | - [ember.js](https://emberjs.com/) 53 | - [ember-cli](https://cli.emberjs.com/release/) 54 | - Development Browser Extensions 55 | - [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 56 | - [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 57 | -------------------------------------------------------------------------------- /packages/test-app/app/app.ts: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | 5 | // @ts-expect-error fix me 6 | import config from 'test-app/config/environment'; 7 | 8 | import 'ember-cached-decorator-polyfill'; 9 | 10 | export default class App extends Application { 11 | modulePrefix = config.modulePrefix; 12 | podModulePrefix = config.podModulePrefix; 13 | Resolver = Resolver; 14 | } 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | -------------------------------------------------------------------------------- /packages/test-app/app/application/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import config from '../config/environment'; 3 | import { inject as service } from '@ember/service'; 4 | 5 | const versionRegExp = /\d+[.]\d+[.]\d+(?:-(?:alpha|beta|rc)\.\d+)?/; 6 | const { emberConcurrencyVersion } = config; 7 | 8 | export default class ApplicationController extends Controller { 9 | addonVersion = emberConcurrencyVersion.match(versionRegExp)[0]; 10 | 11 | @service notifications; 12 | } 13 | -------------------------------------------------------------------------------- /packages/test-app/app/application/route.js: -------------------------------------------------------------------------------- 1 | import { getOwner } from '@ember/application'; 2 | import Route from '@ember/routing/route'; 3 | import { inject as service } from '@ember/service'; 4 | import ENV from 'test-app/config/environment'; 5 | 6 | export default class ApplicationRoute extends Route { 7 | @service router; 8 | 9 | beforeModel() { 10 | const fastboot = getOwner(this).lookup('service:fastboot'); 11 | 12 | if (ENV.environment !== 'test' && (!fastboot || !fastboot.isFastBoot)) { 13 | this.router.on('didTransition', () => { 14 | window.scrollTo(0, 0); 15 | }); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/test-app/app/application/template.hbs: -------------------------------------------------------------------------------- 1 | 24 | 25 | {{outlet}} 26 | 27 |
28 | {{#each this.notifications.messages as |m|}} 29 |
30 | {{m.message}} 31 |
32 | {{/each}} 33 |
34 | -------------------------------------------------------------------------------- /packages/test-app/app/components/ajax-throttling-example/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { enqueueTask, task, timeout } from 'ember-concurrency'; 3 | import { tracked } from '@glimmer/tracking'; 4 | 5 | // BEGIN-SNIPPET ajax-throttling 6 | function loopingAjaxTask(id, color) { 7 | return function* () { 8 | while (true) { 9 | this.log(color, `Task ${id}: making AJAX request`); 10 | yield this.ajaxTask.perform(); 11 | this.log(color, `Task ${id}: Done, sleeping.`); 12 | yield timeout(2000); 13 | } 14 | }; 15 | } 16 | 17 | export default class AjaxThrottlingExampleComponent extends Component { 18 | tagName = ''; 19 | @tracked logs = []; 20 | 21 | ajaxTask = enqueueTask({ maxConcurrency: 3 }, async () => { 22 | // simulate slow AJAX 23 | await timeout(2000 + 2000 * Math.random()); 24 | return {}; 25 | }); 26 | 27 | @task({ on: 'init' }) task0 = loopingAjaxTask(0, '#0000FF'); 28 | @task({ on: 'init' }) task1 = loopingAjaxTask(1, '#8A2BE2'); 29 | @task({ on: 'init' }) task2 = loopingAjaxTask(2, '#A52A2A'); 30 | @task({ on: 'init' }) task3 = loopingAjaxTask(3, '#DC143C'); 31 | @task({ on: 'init' }) task4 = loopingAjaxTask(4, '#20B2AA'); 32 | @task({ on: 'init' }) task5 = loopingAjaxTask(5, '#FF1493'); 33 | @task({ on: 'init' }) task6 = loopingAjaxTask(6, '#228B22'); 34 | @task({ on: 'init' }) task7 = loopingAjaxTask(7, '#DAA520'); 35 | 36 | log(color, message) { 37 | this.logs = [...this.logs, { color, message }].slice(-7); 38 | } 39 | } 40 | // END-SNIPPET 41 | -------------------------------------------------------------------------------- /packages/test-app/app/components/ajax-throttling-example/template.hbs: -------------------------------------------------------------------------------- 1 |
2 |
    3 | {{#each this.logs as |log|}} 4 |
  • {{log.message}}
  • 5 | {{/each}} 6 |
7 |
8 | -------------------------------------------------------------------------------- /packages/test-app/app/components/caps-marquee/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { task, timeout } from 'ember-concurrency'; 3 | import { tracked } from '@glimmer/tracking'; 4 | 5 | function capitalizeAt(text, i) { 6 | let capsLetter = text.charAt(i).toUpperCase(); 7 | let before = text.slice(0, i); 8 | let after = text.slice(i + 1); 9 | return before + capsLetter + after; 10 | } 11 | 12 | export default class CapsMarqueeComponent extends Component { 13 | tagName = ''; 14 | text = null; 15 | @tracked formattedText = ''; 16 | 17 | // BEGIN-SNIPPET caps-marquee 18 | marqueeLoop = task({ on: 'init' }, async () => { 19 | let text = this.text; 20 | while (true) { 21 | this.formattedText = text; 22 | await timeout(1500); 23 | for (let i = 0; i < text.length; ++i) { 24 | this.formattedText = capitalizeAt(text, i); 25 | await timeout(50); 26 | } 27 | } 28 | }); 29 | // END-SNIPPET 30 | } 31 | -------------------------------------------------------------------------------- /packages/test-app/app/components/caps-marquee/template.hbs: -------------------------------------------------------------------------------- 1 | {{this.formattedText}} 2 | -------------------------------------------------------------------------------- /packages/test-app/app/components/code-snippet.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { getCodeSnippet } from 'ember-code-snippet'; 3 | import { cached } from '@glimmer/tracking'; 4 | 5 | import CodeBlock from 'ember-prism/components/code-block'; 6 | 7 | type Signature = { 8 | Args: { 9 | name: string; 10 | }; 11 | }; 12 | 13 | export default class CodeTemplateToggleComponent extends Component { 14 | @cached 15 | get snippet() { 16 | return getCodeSnippet(this.args.name); 17 | } 18 | 19 | get language() { 20 | if (this.snippet.language === 'handlebars') { 21 | return 'markup'; 22 | } 23 | 24 | return this.snippet.language; 25 | } 26 | 27 | 34 | } 35 | -------------------------------------------------------------------------------- /packages/test-app/app/components/code-template-toggle/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { action } from '@ember/object'; 3 | import { guidFor } from '@ember/object/internals'; 4 | 5 | export default class CodeTemplateToggleComponent extends Component { 6 | tagName = ''; 7 | id = guidFor(this); 8 | toggleDescription = 'Toggle JS / Template'; 9 | showCode = true; 10 | _toggleTimer = null; 11 | 12 | maxHeight = 0; 13 | resizeObserver; 14 | 15 | didInsertElement() { 16 | super.didInsertElement(...arguments); 17 | const element = document.getElementById(this.id); 18 | if (!element) { 19 | return; 20 | } 21 | 22 | const resizeObserver = this._createResizeObserver((maxHeight) => { 23 | const toggle = element.querySelector('.code-template-toggle'); 24 | if (toggle) { 25 | toggle.style.height = `${maxHeight}px`; 26 | } 27 | }); 28 | this.set('resizeObserver', resizeObserver); 29 | 30 | const sectionToggles = element.querySelectorAll( 31 | '.code-template-toggle-section', 32 | ); 33 | 34 | sectionToggles.forEach((sectionToggle) => { 35 | resizeObserver.observe(sectionToggle); 36 | }); 37 | } 38 | 39 | willDestroyElement() { 40 | super.willDestroyElement(...arguments); 41 | this.resizeObserver.disconnect(); 42 | } 43 | 44 | @action 45 | toggle() { 46 | this.toggleProperty('showCode'); 47 | } 48 | 49 | _createResizeObserver(onMaxHeightChange) { 50 | return new ResizeObserver((entries) => { 51 | const maxHeight = entries 52 | .map((entry) => entry.contentRect.height) 53 | .reduce((a, b) => Math.max(a, b)); 54 | 55 | if (maxHeight > this.maxHeight) { 56 | this.maxHeight = maxHeight; 57 | onMaxHeightChange && onMaxHeightChange(maxHeight); 58 | } 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/test-app/app/components/code-template-toggle/template.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 8 |
9 | 10 | {{this.toggleDescription}} 11 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /packages/test-app/app/components/concurrency-graph/template.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | {{#if @task.isRunning}} 5 | 6 | {{/if}} 7 | 8 | 9 | {{#each this.trackers as |tracker|}} 10 | 11 | {{#let (pick-from this.colors tracker.id) as |color|}} 12 | {{#if tracker.hasStarted}} 13 | 20 | {{/if}} 21 | 22 | {{#let (scale (subtract tracker.performTime this.lowerLimit) this.lowerLimit this.upperLimit) as |x|}} 23 | 30 | {{tracker.state}} 31 | 32 | {{#let (pick-from this.labelHeights tracker.id) as |y|}} 33 | 34 | {{/let}} 35 | {{/let}} 36 | {{/let}} 37 | 38 | {{/each}} 39 | 40 | {{#let (scale this.timeElapsed this.lowerLimit this.upperLimit) as |x|}} 41 | 42 | {{/let}} 43 | 44 |
45 | -------------------------------------------------------------------------------- /packages/test-app/app/components/count-up/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { task, timeout } from 'ember-concurrency'; 3 | 4 | export default class CountUpComponent extends Component { 5 | tagName = ''; 6 | count = 0; 7 | 8 | // BEGIN-SNIPPET count-up 9 | countUp = task({ on: 'init' }, async () => { 10 | while (true) { 11 | this.incrementProperty('count'); 12 | await timeout(100); 13 | } 14 | }); 15 | // END-SNIPPET 16 | } 17 | -------------------------------------------------------------------------------- /packages/test-app/app/components/count-up/template.hbs: -------------------------------------------------------------------------------- 1 | {{this.count}} 2 | -------------------------------------------------------------------------------- /packages/test-app/app/components/github-edit/component.js: -------------------------------------------------------------------------------- 1 | import { computed } from '@ember/object'; 2 | import { inject as service } from '@ember/service'; 3 | import Component from '@ember/component'; 4 | 5 | export default class extends Component { 6 | @service router; 7 | tagName = ''; 8 | 9 | @computed('router.currentRouteName') 10 | get href() { 11 | let path = this.router.currentRouteName; 12 | if (!path) { 13 | // `routing` doesn't exist for old ember versions via ember-try 14 | return '#'; 15 | } 16 | path = path.replace(/\./g, '/'); 17 | return `https://github.com/machty/ember-concurrency/edit/master/packages/test-app/app/${path}/template.hbs`; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/test-app/app/components/github-edit/template.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/test-app/app/components/loading-spinner/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | 3 | export default class LoadingSpinner extends Component { 4 | tagName = ''; 5 | } 6 | -------------------------------------------------------------------------------- /packages/test-app/app/components/loading-spinner/template.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 15 | 16 | 17 | 23 | 29 | 30 | 31 | 36 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /packages/test-app/app/components/my-button.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { action } from '@ember/object'; 3 | import { didCancel } from 'ember-concurrency'; 4 | 5 | export default class MyButtonComponent extends Component { 6 | tagName = ''; 7 | 8 | @action onClick() { 9 | let val = this.action(3, 4); 10 | if (!val) { 11 | return; 12 | } 13 | val 14 | .then((v) => { 15 | if (v !== 10) { 16 | throw new Error("returned value wasn't 10"); 17 | } 18 | }) 19 | .catch((e) => { 20 | if (!didCancel(e)) { 21 | throw e; 22 | } 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/test-app/app/components/nav-header/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | 3 | export default class NavHeaderComponent extends Component { 4 | tagName = ''; 5 | } 6 | -------------------------------------------------------------------------------- /packages/test-app/app/components/nav-header/template.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#if @prevTopic}} 3 |

4 | Previous: {{@prevTopic.title}} 5 |

6 | {{/if}} 7 | 8 | {{#if @nextTopic}} 9 |

10 | Next: {{@nextTopic.title}} 11 |

12 | {{/if}} 13 |
14 | -------------------------------------------------------------------------------- /packages/test-app/app/components/press-and-hold-button/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | 3 | // BEGIN-SNIPPET increment-button 4 | export default class PressAndHoldButtonComponent extends Component { 5 | tagName = ''; 6 | } 7 | // END-SNIPPET 8 | -------------------------------------------------------------------------------- /packages/test-app/app/components/press-and-hold-button/template.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET increment-button }} 2 | 12 | {{! END-SNIPPET }} 13 | -------------------------------------------------------------------------------- /packages/test-app/app/components/scrambled-text/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { task, timeout } from 'ember-concurrency'; 4 | 5 | // from http://stackoverflow.com/a/3943985/914123 6 | function scramble(word) { 7 | var a = word.split(''), 8 | n = a.length; 9 | 10 | for (var i = n - 1; i > 0; i--) { 11 | var j = Math.floor(Math.random() * (i + 1)); 12 | var tmp = a[i]; 13 | a[i] = a[j]; 14 | a[j] = tmp; 15 | } 16 | return a.join(''); 17 | } 18 | 19 | export default class ScrambledTextComponent extends Component { 20 | tagName = ''; 21 | text = null; 22 | @tracked scrambledText = null; 23 | 24 | // BEGIN-SNIPPET scrambled-text 25 | startScrambling = task({ on: 'init' }, async () => { 26 | let text = this.text; 27 | while (true) { 28 | let pauseTime = 140; 29 | while (pauseTime > 5) { 30 | this.scrambledText = scramble(text); 31 | await timeout(pauseTime); 32 | pauseTime = pauseTime * 0.95; 33 | } 34 | this.scrambledText = text; 35 | await timeout(1500); 36 | } 37 | }); 38 | // END-SNIPPET 39 | } 40 | -------------------------------------------------------------------------------- /packages/test-app/app/components/scrambled-text/template.hbs: -------------------------------------------------------------------------------- 1 | {{this.scrambledText}} 2 | -------------------------------------------------------------------------------- /packages/test-app/app/components/shared-tutorial/component.js: -------------------------------------------------------------------------------- 1 | import { A } from '@ember/array'; 2 | import Component from '@ember/component'; 3 | import { action } from '@ember/object'; 4 | import { timeout } from 'ember-concurrency'; 5 | 6 | const PREFIXES = [ 7 | 'Tomster', 8 | 'Glimmer', 9 | 'Transclusion', 10 | 'Zoey', 11 | 'Flux', 12 | 'Reducer', 13 | ]; 14 | 15 | const SUFFIXES = [ 16 | 'Mart', 17 | ' Central', 18 | 's ᴙ Us', 19 | 'beds n stuff', 20 | 'potle', 21 | ' Donuts', 22 | ]; 23 | 24 | function randomFrom(array) { 25 | return array[Math.floor(Math.random() * array.length)]; 26 | } 27 | 28 | class Store { 29 | async getNearbyStores() { 30 | let num = 3; //Math.floor(Math.random() * 2) + 5; 31 | let stores = []; 32 | for (let i = 0; i < num; ++i) { 33 | let name = randomFrom(PREFIXES) + randomFrom(SUFFIXES); 34 | stores.push({ 35 | lat: Math.random * 60, 36 | long: Math.random * 60, 37 | name, 38 | distance: Math.random().toFixed(2), 39 | }); 40 | } 41 | 42 | await timeout(800); 43 | return { stores }; 44 | } 45 | } 46 | 47 | class Geolocation { 48 | async getCoords() { 49 | await timeout(800); 50 | 51 | return { 52 | lat: Math.random * 60, 53 | long: Math.random * 60, 54 | }; 55 | } 56 | } 57 | 58 | export default class SharedTutorialComponent extends Component { 59 | tagName = ''; 60 | 61 | logs = A(); 62 | formData = { 63 | user: 'machty', 64 | amount: '9.99', 65 | }; 66 | showTemplate = false; 67 | 68 | geolocation = new Geolocation(); 69 | store = new Store(); 70 | 71 | @action 72 | toggleTemplate() { 73 | this.toggleProperty('showTemplate'); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/test-app/app/components/task-function-syntax-1/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { task, timeout } from 'ember-concurrency'; 4 | 5 | export default class TaskFunctionSyntaxComponent1 extends Component { 6 | tagName = ''; 7 | @tracked status = null; 8 | 9 | // BEGIN-SNIPPET task-function-syntax-1 10 | waitAFewSeconds = task(async () => { 11 | this.status = 'Gimme one second...'; 12 | await timeout(1000); 13 | this.status = 'Gimme one more second...'; 14 | await timeout(1000); 15 | this.status = "OK, I'm done."; 16 | }); 17 | // END-SNIPPET 18 | } 19 | -------------------------------------------------------------------------------- /packages/test-app/app/components/task-function-syntax-1/template.hbs: -------------------------------------------------------------------------------- 1 |

2 | 3 | {{this.status}} 4 |

-------------------------------------------------------------------------------- /packages/test-app/app/components/task-function-syntax-2/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { task } from 'ember-concurrency'; 4 | 5 | export default class TaskFunctionSyntaxComponent2 extends Component { 6 | tagName = ''; 7 | @tracked status = null; 8 | 9 | // BEGIN-SNIPPET task-function-syntax-2 10 | pickRandomNumbers = task(async () => { 11 | let nums = []; 12 | for (let i = 0; i < 3; i++) { 13 | nums.push(Math.floor(Math.random() * 10)); 14 | } 15 | 16 | this.status = `My favorite numbers: ${nums.join(', ')}`; 17 | }); 18 | // END-SNIPPET 19 | } 20 | -------------------------------------------------------------------------------- /packages/test-app/app/components/task-function-syntax-2/template.hbs: -------------------------------------------------------------------------------- 1 |

2 | 4 | {{this.status}} 5 |

-------------------------------------------------------------------------------- /packages/test-app/app/components/task-function-syntax-3/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { task, timeout } from 'ember-concurrency'; 4 | 5 | export default class TaskFunctionSyntaxComponent3 extends Component { 6 | tagName = ''; 7 | @tracked status = null; 8 | 9 | // BEGIN-SNIPPET task-function-syntax-3 10 | myTask = task(async () => { 11 | this.status = `Thinking...`; 12 | let promise = timeout(1000).then(() => 123); 13 | let resolvedValue = await promise; 14 | this.status = `The value is ${resolvedValue}`; 15 | }); 16 | // END-SNIPPET 17 | } 18 | -------------------------------------------------------------------------------- /packages/test-app/app/components/task-function-syntax-3/template.hbs: -------------------------------------------------------------------------------- 1 |

2 | 3 | {{this.status}} 4 |

-------------------------------------------------------------------------------- /packages/test-app/app/components/task-function-syntax-4/component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { task, timeout } from 'ember-concurrency'; 4 | 5 | export default class TaskFunctionSyntaxComponent4 extends Component { 6 | tagName = ''; 7 | @tracked status = null; 8 | 9 | // BEGIN-SNIPPET task-function-syntax-4 10 | myTask = task(async () => { 11 | this.status = `Thinking...`; 12 | try { 13 | await timeout(1000).then(() => { 14 | throw 'Ahhhhh!!!!'; 15 | }); 16 | this.status = `This does not get used!`; 17 | } catch (e) { 18 | this.status = `Caught value: ${e}`; 19 | } 20 | }); 21 | // END-SNIPPET 22 | } 23 | -------------------------------------------------------------------------------- /packages/test-app/app/components/task-function-syntax-4/template.hbs: -------------------------------------------------------------------------------- 1 |

2 | 3 | {{this.status}} 4 |

-------------------------------------------------------------------------------- /packages/test-app/app/components/task-lifecycle-events-example/template.hbs: -------------------------------------------------------------------------------- 1 |
2 |
    3 | {{#each this.logs as |log|}} 4 |
  • {{log.message}}
  • 5 | {{/each}} 6 |
7 |
8 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tests/basic-template-imports.gts: -------------------------------------------------------------------------------- 1 | import GlimmerComponent from '@glimmer/component'; 2 | import { task, timeout } from 'ember-concurrency'; 3 | import { tracked } from '@glimmer/tracking'; 4 | 5 | import { fn } from '@ember/helper'; 6 | import { on } from '@ember/modifier'; 7 | 8 | import perform from 'ember-concurrency/helpers/perform'; 9 | import curryTask from 'ember-concurrency/helpers/task'; 10 | import cancelAll from 'ember-concurrency/helpers/cancel-all'; 11 | 12 | import RSVP from 'rsvp'; 13 | 14 | interface Signature { 15 | Args: { 16 | rating: null | number; 17 | updateRating: (rating: number) => void; 18 | }; 19 | Element: HTMLDivElement; 20 | } 21 | 22 | export default class BasicTemplateImports extends GlimmerComponent { 23 | @tracked value = ''; 24 | 25 | valueTask = task(async (value) => { 26 | this.value = value; 27 | }); 28 | 29 | promiseTask = task(async (_event, value) => { 30 | this.value = value; 31 | await new RSVP.Promise(() => {}); 32 | }); 33 | 34 | 65 | } 66 | 67 | declare module '@glint/environment-ember-loose/registry' { 68 | export default interface Registry { 69 | BasicTemplateImports: typeof BasicTemplateImports; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-0/component.js: -------------------------------------------------------------------------------- 1 | import TutorialComponent from '../shared-tutorial/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | 4 | // BEGIN-SNIPPET better-syntax-1 5 | import { action } from '@ember/object'; 6 | 7 | export default class Tutorial0 extends TutorialComponent { 8 | @tracked result = null; 9 | 10 | @action 11 | async findStores() { 12 | let geolocation = this.geolocation; 13 | let store = this.store; 14 | 15 | let coords = await geolocation.getCoords(); 16 | let result = await store.getNearbyStores(coords); 17 | 18 | this.result = result; 19 | } 20 | } 21 | // END-SNIPPET 22 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-0/template.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{! BEGIN-SNIPPET better-syntax-1 }} 4 | 7 | 8 | {{#if this.result}} 9 | {{#each this.result.stores as |s|}} 10 |
  • 11 | {{s.name}}: 12 | {{s.distance}} miles away 13 |
  • 14 | {{/each}} 15 | {{/if}} 16 | {{! END-SNIPPET }} 17 | 18 | Example 19 |
    20 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-1/component.js: -------------------------------------------------------------------------------- 1 | import { tracked } from '@glimmer/tracking'; 2 | import TutorialComponent from '../shared-tutorial/component'; 3 | 4 | // BEGIN-SNIPPET better-syntax-2 5 | import { action } from '@ember/object'; 6 | 7 | export default class Tutorial1 extends TutorialComponent { 8 | @tracked result = null; 9 | @tracked isFindingStores = false; // ++ 10 | 11 | @action 12 | async findStores() { 13 | let geolocation = this.geolocation; 14 | let store = this.store; 15 | 16 | this.isFindingStores = true; // ++ 17 | 18 | let coords = await geolocation.getCoords(); 19 | let result = await store.getNearbyStores(coords); 20 | 21 | this.result = result; 22 | this.isFindingStores = false; // ++ 23 | } 24 | } 25 | // END-SNIPPET 26 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-1/template.hbs: -------------------------------------------------------------------------------- 1 |
    2 | 3 | {{! BEGIN-SNIPPET better-syntax-2 }} 4 | 11 | 12 | {{#if this.result}} 13 | {{#each this.result.stores as |s|}} 14 |
  • 15 | {{s.name}}: 16 | {{s.distance}} miles away 17 |
  • 18 | {{/each}} 19 | {{/if}} 20 | {{! END-SNIPPET }} 21 | 22 | Example 23 |
    24 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-2/component.js: -------------------------------------------------------------------------------- 1 | import { tracked } from '@glimmer/tracking'; 2 | import TutorialComponent from '../shared-tutorial/component'; 3 | 4 | // BEGIN-SNIPPET better-syntax-3 5 | import { action } from '@ember/object'; 6 | 7 | export default class Tutorial2 extends TutorialComponent { 8 | @tracked result = null; 9 | @tracked isFindingStores = false; 10 | 11 | @action 12 | async findStores() { 13 | if (this.isFindingStores) { 14 | return; 15 | } // ++ 16 | 17 | let geolocation = this.geolocation; 18 | let store = this.store; 19 | 20 | this.isFindingStores = true; 21 | 22 | let coords = await geolocation.getCoords(); 23 | let result = await store.getNearbyStores(coords); 24 | 25 | this.result = result; 26 | this.isFindingStores = false; 27 | } 28 | } 29 | // END-SNIPPET 30 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-2/template.hbs: -------------------------------------------------------------------------------- 1 |
    2 | 3 | {{! BEGIN-SNIPPET better-syntax-3 }} 4 | 10 | 11 | {{#if this.result}} 12 | {{#each this.result.stores as |s|}} 13 |
  • 14 | {{s.name}}: 15 | {{s.distance}} miles away 16 |
  • 17 | {{/each}} 18 | {{/if}} 19 | {{! END-SNIPPET }} 20 | 21 | Example 22 |
    23 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-3/component.js: -------------------------------------------------------------------------------- 1 | import { tracked } from '@glimmer/tracking'; 2 | import TutorialComponent from '../shared-tutorial/component'; 3 | 4 | // BEGIN-SNIPPET better-syntax-4 5 | import { action } from '@ember/object'; 6 | 7 | export default class Tutorial3 extends TutorialComponent { 8 | @tracked result = null; 9 | @tracked isFindingStores = false; 10 | 11 | @action 12 | async findStores() { 13 | if (this.isFindingStores) { 14 | return; 15 | } 16 | 17 | let geolocation = this.geolocation; 18 | let store = this.store; 19 | 20 | this.isFindingStores = true; 21 | 22 | let coords = await geolocation.getCoords(); 23 | let result = await store.getNearbyStores(coords); 24 | 25 | if (this.isDestroyed) { 26 | return; 27 | } // ++ 28 | 29 | this.result = result; 30 | this.isFindingStores = false; 31 | } 32 | } 33 | // END-SNIPPET 34 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-3/template.hbs: -------------------------------------------------------------------------------- 1 |
    2 | 3 | {{! BEGIN-SNIPPET better-syntax-4 }} 4 | 10 | 11 | {{#if this.result}} 12 | {{#each this.result.stores as |s|}} 13 |
  • 14 | {{s.name}}: 15 | {{s.distance}} miles away 16 |
  • 17 | {{/each}} 18 | {{/if}} 19 | {{! END-SNIPPET }} 20 | 21 | Example 22 |
    23 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-4/component.js: -------------------------------------------------------------------------------- 1 | import { tracked } from '@glimmer/tracking'; 2 | import TutorialComponent from '../shared-tutorial/component'; 3 | 4 | // BEGIN-SNIPPET better-syntax-5 5 | import { action } from '@ember/object'; 6 | 7 | export default class Tutorial4 extends TutorialComponent { 8 | @tracked result = null; 9 | @tracked isFindingStores = false; 10 | 11 | @action 12 | async findStores() { 13 | if (this.isFindingStores) { 14 | return; 15 | } 16 | 17 | let geolocation = this.geolocation; 18 | let store = this.store; 19 | 20 | this.isFindingStores = true; 21 | 22 | try { 23 | // ++ 24 | let coords = await geolocation.getCoords(); 25 | let result = await store.getNearbyStores(coords); 26 | 27 | if (this.isDestroyed) { 28 | return; 29 | } 30 | 31 | this.result = result; 32 | } finally { 33 | // ++ 34 | if (!this.isDestroyed) { 35 | // ++ 36 | this.isFindingStores = false; // ++ 37 | } // ++ 38 | } // ++ 39 | } 40 | } 41 | // END-SNIPPET 42 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-4/template.hbs: -------------------------------------------------------------------------------- 1 |
    2 | 3 | {{! BEGIN-SNIPPET better-syntax-5 }} 4 | 10 | 11 | {{#if this.result}} 12 | {{#each this.result.stores as |s|}} 13 |
  • 14 | {{s.name}}: 15 | {{s.distance}} miles away 16 |
  • 17 | {{/each}} 18 | {{/if}} 19 | {{! END-SNIPPET }} 20 | 21 | Example 22 |
    23 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-5/component.js: -------------------------------------------------------------------------------- 1 | import { tracked } from '@glimmer/tracking'; 2 | import TutorialComponent from '../shared-tutorial/component'; 3 | 4 | // BEGIN-SNIPPET better-syntax-6 5 | import { action } from '@ember/object'; 6 | 7 | export default class Tutorial5 extends TutorialComponent { 8 | @tracked result = null; 9 | @tracked isFindingStores = false; 10 | 11 | @action 12 | async findStores() { 13 | if (this.isFindingStores) { 14 | return; 15 | } 16 | 17 | let geolocation = this.geolocation; 18 | let store = this.store; 19 | 20 | this.isFindingStores = true; 21 | 22 | try { 23 | let coords = await geolocation.getCoords(); 24 | let result = await store.getNearbyStores(coords); 25 | 26 | if (this.isDestroyed) { 27 | return; 28 | } 29 | 30 | this.result = result; 31 | } finally { 32 | if (!this.isDestroyed) { 33 | this.isFindingStores = false; 34 | } 35 | } 36 | } 37 | } 38 | // END-SNIPPET 39 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-5/template.hbs: -------------------------------------------------------------------------------- 1 |
    2 | 3 | {{! BEGIN-SNIPPET better-syntax-6 }} 4 | 10 | 11 | {{#if this.result}} 12 | {{#each this.result.stores as |s|}} 13 |
  • 14 | {{s.name}}: 15 | {{s.distance}} miles away 16 |
  • 17 | {{/each}} 18 | {{/if}} 19 | {{! END-SNIPPET }} 20 | 21 | Example 22 |
    23 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-6/component.js: -------------------------------------------------------------------------------- 1 | import { tracked } from '@glimmer/tracking'; 2 | import TutorialComponent from '../shared-tutorial/component'; 3 | 4 | // BEGIN-SNIPPET better-syntax-7 5 | import { task } from 'ember-concurrency'; 6 | 7 | export default class Tutorial6 extends TutorialComponent { 8 | @tracked result = null; 9 | 10 | findStores = task(async () => { 11 | let geolocation = this.geolocation; 12 | let store = this.store; 13 | 14 | let coords = await geolocation.getCoords(); 15 | let result = await store.getNearbyStores(coords); 16 | this.result = result; 17 | }); 18 | } 19 | // END-SNIPPET 20 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-6/template.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{! template-lint-disable block-indentation }} 3 | 4 | {{! BEGIN-SNIPPET better-syntax-7 }} 5 | 9 | 10 | {{#if this.result}} 11 | {{#each this.result.stores as |s|}} 12 |
  • 13 | {{s.name}}: 14 | {{s.distance}} 15 | miles away 16 |
  • 17 | {{/each}} 18 | {{/if}} 19 | {{! END-SNIPPET }} 20 | 21 | Example 22 |
    -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-7/component.js: -------------------------------------------------------------------------------- 1 | import { tracked } from '@glimmer/tracking'; 2 | import TutorialComponent from '../shared-tutorial/component'; 3 | 4 | // BEGIN-SNIPPET better-syntax-8 5 | import { task } from 'ember-concurrency'; 6 | 7 | export default class Tutorial7 extends TutorialComponent { 8 | @tracked result = null; 9 | 10 | findStores = task(async () => { 11 | let geolocation = this.geolocation; 12 | let store = this.store; 13 | 14 | let coords = await geolocation.getCoords(); 15 | let result = await store.getNearbyStores(coords); 16 | this.result = result; 17 | }); 18 | } 19 | // END-SNIPPET 20 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-7/template.hbs: -------------------------------------------------------------------------------- 1 |
    2 | 3 | {{! BEGIN-SNIPPET better-syntax-8 }} 4 | 11 | 12 | {{#if this.result}} 13 | {{#each this.result.stores as |s|}} 14 |
  • 15 | {{s.name}}: 16 | {{s.distance}} 17 | miles away 18 |
  • 19 | {{/each}} 20 | {{/if}} 21 | {{! END-SNIPPET }} 22 | 23 | Example 24 |
    -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-8/component.js: -------------------------------------------------------------------------------- 1 | import { tracked } from '@glimmer/tracking'; 2 | import TutorialComponent from '../shared-tutorial/component'; 3 | 4 | // BEGIN-SNIPPET better-syntax-9 5 | import { task } from 'ember-concurrency'; 6 | 7 | export default class Tutorial8 extends TutorialComponent { 8 | @tracked result = null; 9 | 10 | findStores = task({ drop: true }, async () => { 11 | // ++ 12 | let geolocation = this.geolocation; 13 | let store = this.store; 14 | 15 | let coords = await geolocation.getCoords(); 16 | let result = await store.getNearbyStores(coords); 17 | this.result = result; 18 | }); 19 | } 20 | // END-SNIPPET 21 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-8/template.hbs: -------------------------------------------------------------------------------- 1 |
    2 | 3 | {{! BEGIN-SNIPPET better-syntax-9 }} 4 | 10 | 11 | {{#if this.result}} 12 | {{#each this.result.stores as |s|}} 13 |
  • 14 | {{s.name}}: 15 | {{s.distance}} 16 | miles away 17 |
  • 18 | {{/each}} 19 | {{/if}} 20 | {{! END-SNIPPET }} 21 | 22 | Example 23 |
    -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-9/component.js: -------------------------------------------------------------------------------- 1 | import { tracked } from '@glimmer/tracking'; 2 | import TutorialComponent from '../shared-tutorial/component'; 3 | 4 | // BEGIN-SNIPPET better-syntax-10 5 | import { task } from 'ember-concurrency'; 6 | 7 | export default class Tutorial9 extends TutorialComponent { 8 | @tracked result = null; 9 | 10 | findStores = task({ drop: true }, async () => { 11 | let geolocation = this.geolocation; 12 | let store = this.store; 13 | 14 | let coords = await geolocation.getCoords(); 15 | let result = await store.getNearbyStores(coords); 16 | this.result = result; 17 | }); 18 | } 19 | // END-SNIPPET 20 | -------------------------------------------------------------------------------- /packages/test-app/app/components/tutorial-9/template.hbs: -------------------------------------------------------------------------------- 1 |
    2 | 3 | {{! BEGIN-SNIPPET better-syntax-10 }} 4 | 10 | 11 | {{#if this.result}} 12 | {{#each this.result.stores as |s|}} 13 |
  • 14 | {{s.name}}: 15 | {{s.distance}} 16 | miles away 17 |
  • 18 | {{/each}} 19 | {{/if}} 20 | {{! END-SNIPPET }} 21 | 22 | Example 23 |
    -------------------------------------------------------------------------------- /packages/test-app/app/docs/404/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class NotFoundRoute extends Route { 5 | @service router; 6 | 7 | redirect() { 8 | this.router.transitionTo('docs'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/advanced/encapsulated-task/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { computed } from '@ember/object'; 3 | import { randomWord } from 'test-app/utils'; 4 | 5 | // BEGIN-SNIPPET encapsulated-task-controller 6 | import { task, timeout } from 'ember-concurrency'; 7 | 8 | export default class EncapsulatedTaskController extends Controller { 9 | @task({ enqueue: true }) uploadFile = { 10 | progress: 0, 11 | url: null, 12 | 13 | stateText: computed('progress', function () { 14 | let progress = this.progress; 15 | if (progress < 49) { 16 | return 'Just started...'; 17 | } else if (progress < 100) { 18 | return 'Halfway there...'; 19 | } else { 20 | return 'Done!'; 21 | } 22 | }), 23 | 24 | *perform(makeUrl) { 25 | this.set('url', makeUrl()); 26 | 27 | while (this.progress < 100) { 28 | yield timeout(200); 29 | let newProgress = this.progress + Math.floor(Math.random() * 6) + 5; 30 | this.set('progress', Math.min(100, newProgress)); 31 | } 32 | 33 | return '(upload result data)'; 34 | }, 35 | }; 36 | 37 | makeRandomUrl() { 38 | return `https://www.${randomWord()}.edu`; 39 | } 40 | } 41 | // END-SNIPPET 42 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/advanced/encapsulated-task/template.hbs: -------------------------------------------------------------------------------- 1 |

    Encapsulated Tasks

    2 | 3 |

    4 | Encapsulated tasks have a somewhat murky future are likely to be deprecated in the future. 5 |

    6 | 7 |

    8 | You can read about them in the 9 | old docs. 10 |

    -------------------------------------------------------------------------------- /packages/test-app/app/docs/advanced/lifecycle-events/template.hbs: -------------------------------------------------------------------------------- 1 |

    Task lifecycle events

    2 | 3 |

    4 | ember-concurrency implements a number of lifecycle events that allow 5 | for a host object to react to various changes in task instance lifecycle. 6 | Using lifecycle hooks allows for adding things like instrumentation to tasks 7 | or for separating and reusing error handling code across different tasks. 8 | Using the evented modifier will enable tasks to fire lifecycle 9 | events on their host objects. 10 |

    11 | 12 |

    13 | In the below example, we refactor the 14 | AJAX throttling example to use 15 | task events for logging of the ajaxTask's state changes, instead 16 | of doing it manually in the looping task itself. 17 |

    18 | 19 |

    Live Example

    20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/advanced/task-modifiers/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { computed } from '@ember/object'; 3 | 4 | // BEGIN-SNIPPET task-modifier-benchmark-on-task 5 | import { task, timeout } from 'ember-concurrency'; 6 | 7 | // registerModifer is called in the module defining the modifier, 8 | // so we're really just importing it here for the side-effect. This is mostly for 9 | // terseness in this illustration. You may want to separate defining the modifier 10 | // and registering it with registerModifier, and be explicit about where you 11 | // register (e.g. addon, library, or app initialization) 12 | import 'test-app/task-modifiers/benchmark'; 13 | 14 | let performance = 15 | typeof window !== 'undefined' && window.performance 16 | ? window.performance 17 | : { getEntriesByName() {} }; 18 | 19 | export default class TaskModifiersController extends Controller { 20 | doWork = task({ drop: true, benchmark: true }, async () => { 21 | await timeout(20000 * Math.random()); 22 | }); 23 | 24 | @computed('doWork.isRunning') 25 | get perfEntries() { 26 | if (this.doWork.isRunning) { 27 | return []; 28 | } else { 29 | return performance.getEntriesByName( 30 | 'ember-concurrency.doWork.runtime', 31 | 'measure', 32 | ); 33 | } 34 | } 35 | } 36 | // END-SNIPPET 37 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/cancelation/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | import { tracked } from '@glimmer/tracking'; 4 | import { forever, task } from 'ember-concurrency'; 5 | 6 | // BEGIN-SNIPPET cancelation 7 | export default class CancelationController extends Controller { 8 | @tracked count = 0; 9 | @tracked mostRecent = null; 10 | 11 | myTask = task(async () => { 12 | try { 13 | this.count += 1; 14 | await forever; 15 | } finally { 16 | // finally blocks always get called, 17 | // even when the task is being canceled 18 | this.decrementProperty('count'); 19 | this.count -= 1; 20 | } 21 | }); 22 | 23 | @action 24 | performTask() { 25 | let task = this.myTask; 26 | let taskInstance = task.perform(); 27 | this.mostRecent = taskInstance; 28 | } 29 | 30 | @action 31 | cancelAll() { 32 | this.myTask.cancelAll(); 33 | } 34 | 35 | @action 36 | cancelMostRecent() { 37 | this.mostRecent.cancel(); 38 | } 39 | } 40 | // END-SNIPPET 41 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/child-tasks/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { restartableTask, task, timeout } from 'ember-concurrency'; 4 | 5 | // BEGIN-SNIPPET child-tasks 6 | export default class ChildTasksController extends Controller { 7 | @tracked status = 'Waiting to start'; 8 | 9 | parentTask = restartableTask(async () => { 10 | this.status = '1. Parent: one moment...'; 11 | await timeout(1000); 12 | let value = await this.childTask.perform(); 13 | this.status = `5. Parent: child says "${value}"`; 14 | await timeout(1000); 15 | this.status = '6. Done!'; 16 | }); 17 | 18 | childTask = task(async () => { 19 | this.status = '2. Child: one moment...'; 20 | await timeout(1000); 21 | let value = await this.grandchildTask.perform(); 22 | this.status = `4. Child: grandchild says "${value}"`; 23 | await timeout(1000); 24 | return "What's up"; 25 | }); 26 | 27 | grandchildTask = task(async () => { 28 | this.status = '3. Grandchild: one moment...'; 29 | await timeout(1000); 30 | return 'Hello'; 31 | }); 32 | } 33 | // END-SNIPPET 34 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/child-tasks/template.hbs: -------------------------------------------------------------------------------- 1 |

    Child Tasks

    2 | 3 |

    4 | Tasks can call other tasks by 5 | awaiting the result of 6 | anotherTask.perform(). When this happens, the Parent task will 7 | wait for the Child task to complete before proceeding. If the Parent task is 8 | canceled, the Child task will automatically be canceled as well. 9 |

    10 | 11 |

    Example

    12 | 13 | {{! BEGIN-SNIPPET child-tasks-template }} 14 |
    {{this.status}}
    15 | 16 |
      17 |
    • Parent Task: {{this.parentTask.state}}
    • 18 |
    • Child Task: {{this.childTask.state}}
    • 19 |
    • Grandchild Task: {{this.grandchildTask.state}}
    • 20 |
    21 | 22 | 29 | {{! END-SNIPPET }} 30 | 31 | 32 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/derived-state/controller.js: -------------------------------------------------------------------------------- 1 | import { action, computed } from '@ember/object'; 2 | import Controller from '@ember/controller'; 3 | import { task, timeout } from 'ember-concurrency'; 4 | import { randomWord } from 'test-app/utils'; 5 | 6 | let i = 0; 7 | function* sharedFn(shouldError) { 8 | i++; 9 | 10 | yield timeout(1000); 11 | 12 | let words = [randomWord(), randomWord(), randomWord()]; 13 | let wordsString = `${i}: ${words}`; 14 | 15 | if (shouldError) { 16 | throw new Error(wordsString); 17 | } else { 18 | return wordsString; 19 | } 20 | } 21 | 22 | // BEGIN-SNIPPET completion-state-controller 23 | export default class DerivedStateController extends Controller { 24 | @task doStuff = sharedFn; 25 | @task({ drop: true }) doStuffDrop = sharedFn; 26 | @task({ enqueue: true }) doStuffEnqueue = sharedFn; 27 | @task({ restartable: true }) doStuffRestartable = sharedFn; 28 | 29 | showLessCommon = false; 30 | 31 | tasks = ['doStuff', 'doStuffDrop', 'doStuffEnqueue', 'doStuffRestartable']; 32 | 33 | @computed( 34 | 'commonTaskProperties', 35 | 'lessCommonTaskProperties', 36 | 'showLessCommon', 37 | ) 38 | get taskProperties() { 39 | return [ 40 | ...this.commonTaskProperties, 41 | ...(this.showLessCommon ? this.lessCommonTaskProperties : []), 42 | ]; 43 | } 44 | 45 | commonTaskProperties = ['last', 'lastSuccessful', 'lastErrored']; 46 | 47 | lessCommonTaskProperties = [ 48 | 'lastComplete', 49 | 'lastPerformed', 50 | 'lastIncomplete', 51 | 'lastCanceled', 52 | ]; 53 | 54 | @action 55 | performAll() {} 56 | } 57 | // END-SNIPPET 58 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/encapsulated-task/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class EncapsulatedTaskRoute extends Route { 5 | @service router; 6 | 7 | redirect() { 8 | this.router.transitionTo('docs.advanced.encapsulated-task'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/error-vs-cancelation/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { restartableTask, timeout } from 'ember-concurrency'; 3 | 4 | // BEGIN-SNIPPET error-vs-cancelation 5 | export default class ErrorVsCancelationController extends Controller { 6 | numCompletions = 0; 7 | numErrors = 0; 8 | numFinallys = 0; 9 | 10 | myTask = restartableTask(async (doError) => { 11 | try { 12 | await timeout(1000); 13 | if (doError) { 14 | throw new Error('Boom'); 15 | } 16 | } catch (e) { 17 | this.incrementProperty('numErrors'); 18 | } finally { 19 | this.incrementProperty('numFinallys'); 20 | } 21 | this.incrementProperty('numCompletions'); 22 | }); 23 | } 24 | // END-SNIPPET 25 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/events/template.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/ajax-throttling/template.hbs: -------------------------------------------------------------------------------- 1 |

    Example: AJAX Throttling

    2 | 3 |

    4 | Limiting the number of simultaneous AJAX requests 5 | (or the number of any kind of global, shared resource) 6 | can be accomplished using the maxConcurrency task modifier. 7 |

    8 | 9 |

    10 | In the example below, we render a component with 8 different 11 | concurrently running tasks that each, within an infinite loop, 12 | make (fake) AJAX requests. We've wrapped the code that actually 13 | performs the (fake) AJAX request in a task, and we've annotated 14 | that task with @task({ maxConcurrency: 3 }) to ensure that 15 | no more than 3 AJAX requests can be run at a time (so that 16 | we don't overload the browser). 17 |

    18 | 19 |

    Live Example

    20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/autocomplete/controller.js: -------------------------------------------------------------------------------- 1 | import { isBlank } from '@ember/utils'; 2 | import Controller from '@ember/controller'; 3 | import { restartableTask, task, timeout } from 'ember-concurrency'; 4 | 5 | // BEGIN-SNIPPET debounced-search-with-cancelation 6 | const DEBOUNCE_MS = 250; 7 | export default class AutocompleteController extends Controller { 8 | searchRepo = restartableTask(async (event) => { 9 | const term = event.target.value; 10 | 11 | if (isBlank(term)) { 12 | return []; 13 | } 14 | 15 | // Pause here for DEBOUNCE_MS milliseconds. Because this 16 | // task is `restartable`, if the user starts typing again, 17 | // the current search will be canceled at this point and 18 | // start over from the beginning. This is the 19 | // ember-concurrency way of debouncing a task. 20 | await timeout(DEBOUNCE_MS); 21 | 22 | let url = `https://api.github.com/search/repositories?q=${term}`; 23 | 24 | // We yield an AJAX request and wait for it to complete. If the task 25 | // is restarted before this request completes, the XHR request 26 | // is aborted (open the inspector and see for yourself :) 27 | let json = await this.getJSON.perform(url); 28 | return json.items.slice(0, 10); 29 | }); 30 | 31 | getJSON = task(async (url) => { 32 | let controller = new AbortController(); 33 | let signal = controller.signal; 34 | 35 | try { 36 | let response = await fetch(url, { signal }); 37 | let result = await response.json(); 38 | return result; 39 | 40 | // NOTE: could also write this as 41 | // return yield fetch(url, { signal }).then((response) => response.json()); 42 | // 43 | // either way, the important thing is to yield before returning 44 | // so that the `finally` block doesn't run until after the 45 | // promise resolves (or the task is canceled). 46 | } finally { 47 | controller.abort(); 48 | } 49 | }); 50 | } 51 | // END-SNIPPET 52 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/autocomplete/template.hbs: -------------------------------------------------------------------------------- 1 |

    Debounced Type-Ahead Search

    2 | 3 |

    4 | This advanced example combines multiple ember-concurrency concepts to build a 5 | basic type-ahead search field with the following features: 6 |

    7 | 8 |
      9 |
    • 10 | Debouncing: the browser won't make network requests until the user has 11 | stopped typing for more than 250ms. This is accomplished by combining the 12 | restartable 13 | task modifier with a 14 | yield timeout(250) 15 | at the beginning of the task. 16 |
    • 17 |
    • 18 | Fetch cancelation: if the user starts typing while a prior fetch request is 19 | underway, that fetch request will be canceled to save network resources 20 | (this is accomplished via the 21 | try / finally cancelation pattern). 22 |
    • 23 |
    • 24 | Use 25 | Derived State 26 | to display both a loading spinner and the final search results without using 27 | a single 28 | .set(). 29 |
    • 30 |
    31 | 32 |
    Live Example
    33 | 34 |

    35 | Please mind the GitHub API quota :) 36 |

    37 | 38 |

    39 | {{! BEGIN-SNIPPET debounced-search-with-cancelation-template }} 40 | 49 | 50 | {{#if this.searchRepo.isRunning}} 51 | 52 | {{/if}} 53 | 54 |

      55 | {{#each this.searchRepo.lastSuccessful.value as |repo|}} 56 |
    • {{repo.full_name}}
    • 57 | {{/each}} 58 |
    59 | {{! END-SNIPPET }} 60 |

    61 | 62 |
    JavaScript
    63 | 64 | 65 | 66 |
    Template
    67 | 68 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/increment-buttons/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { task, timeout } from 'ember-concurrency'; 3 | 4 | // BEGIN-SNIPPET increment-button-task 5 | export default class IncrementButtonsController extends Controller { 6 | count = 0; 7 | 8 | incrementBy = task(async (inc) => { 9 | let speed = 400; 10 | while (true) { 11 | this.incrementProperty('count', inc); 12 | await timeout(speed); 13 | speed = Math.max(50, speed * 0.8); 14 | } 15 | }); 16 | } 17 | // END-SNIPPET 18 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/increment-buttons/template.hbs: -------------------------------------------------------------------------------- 1 |

    Accelerating Increment / Decrement Buttons

    2 | 3 |

    4 | This example demonstrates a few different concepts: 5 |

    6 | 7 |
      8 |
    • 9 | Tricky time-based operations like acceleration are simplified by the 10 | sequential style of task functions 11 |
    • 12 |
    • 13 | You can use 14 | taskName.perform 15 | in place of anywhere you might want to use a classic Ember action. 16 |
    • 17 |
    18 | 19 |
    Live Example
    20 | 21 |

    Num: {{this.count}}

    22 | 23 |

    (Hold down the buttons to accelerate.)

    24 | 25 | {{! BEGIN-SNIPPET press-and-hold-buttons }} 26 |

    27 | 31 | --Decrease 32 | 33 | 34 | 38 | Increase++ 39 | 40 |

    41 | {{! END-SNIPPET }} 42 | 43 |
    JavaScript (task)
    44 | 45 | 46 | 47 |
    Template
    48 | 49 | 50 | 51 |
    Template (button component)
    52 | 53 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/index/template.hbs: -------------------------------------------------------------------------------- 1 |

    Examples

    2 | 3 |

    4 | What better way to familiarize yourself with 5 | ember-concurrency than to check out 6 | the slew of examples on the left? 7 |

    8 | 9 |

    10 | Also, if you can't find the example or answer you're looking for, 11 | please open an issue 12 | or ping @machty on Twitter and 13 | he'll cook one up for you :). 14 |

    15 | 16 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/joining-tasks/controller.js: -------------------------------------------------------------------------------- 1 | import { makeArray } from '@ember/array'; 2 | import Controller from '@ember/controller'; 3 | import { randomWord } from 'test-app/utils'; 4 | 5 | // BEGIN-SNIPPET joining-tasks 6 | import { task, timeout, all, race } from 'ember-concurrency'; 7 | import { tracked } from '@glimmer/tracking'; 8 | const methods = { all, race }; 9 | 10 | export default class JoiningTasksController extends Controller { 11 | @tracked childTasks = null; 12 | @tracked colors = ['#ff8888', '#88ff88', '#8888ff']; 13 | @tracked status = 'Waiting...'; 14 | @tracked id = null; 15 | @tracked percent = null; 16 | 17 | parent = task({ restartable: true }, async (methodName) => { 18 | let allOrRace = methods[methodName]; 19 | let childTasks = []; 20 | 21 | for (let id = 0; id < 5; ++id) { 22 | childTasks.push(this.child.perform(id)); 23 | } 24 | 25 | this.childTasks = childTasks; 26 | this.status = 'Waiting for child tasks to complete...'; 27 | let words = await allOrRace(childTasks); 28 | this.status = `Done: ${makeArray(words).join(', ')}`; 29 | }); 30 | 31 | @task({ enqueue: true, maxConcurrency: 3 }) 32 | child = { 33 | percent: 0, 34 | id: null, 35 | 36 | *perform(id) { 37 | this.id = id; 38 | while (this.percent < 100) { 39 | yield timeout(Math.random() * 100 + 100); 40 | let newPercent = Math.min( 41 | 100, 42 | Math.floor(this.percent + Math.random() * 20), 43 | ); 44 | this.percent = newPercent; 45 | } 46 | return randomWord(); 47 | }, 48 | }; 49 | } 50 | // END-SNIPPET 51 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/loading-ui/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { dropTask, timeout } from 'ember-concurrency'; 4 | 5 | // BEGIN-SNIPPET loading-ui-controller 6 | export default class LoadingUIController extends Controller { 7 | @tracked result = null; 8 | 9 | askQuestion = dropTask(async () => { 10 | await timeout(1000); 11 | this.result = Math.random(); 12 | }); 13 | } 14 | // END-SNIPPET 15 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/loading-ui/template.hbs: -------------------------------------------------------------------------------- 1 |

    Loading UI While a Task is Running

    2 | 3 |

    4 | Reining in undesired concurrency is partly what 5 | ember-concurrency 6 | has to offer. The other part is making it easy to build UI around asynchronous 7 | tasks. 8 |

    9 | 10 |

    11 | For simple cases where you just need to display a loading dialog or disable a 12 | button while a task is running, you can make use of the 13 | .isIdle 14 | property of a task. This property is false when the task is running, and true 15 | otherwise. This eliminates a lot of the boilerplate of setting a property at 16 | the beginning of some async operation, and unsetting when the operation 17 | completes. Also, because the task in the example below uses the 18 | drop 19 | modifier (see 20 | Task Modifiers), there's no 21 | need to write a guard at the beginning of the task to return early if the task 22 | is already running. 23 |

    24 | 25 |

    Live Example

    26 | 27 |

    28 | What is the meaning of life? 29 | {{#if this.result}} Answer: {{this.result}} {{/if}} 30 | 31 |

    32 | 33 |

    34 | {{! BEGIN-SNIPPET ask-button }} 35 | 47 | {{! END-SNIPPET }} 48 |

    49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/route-tasks/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class RouteTasksController extends Controller { 4 | ids = [1, 2, 3, 4, 10, 50, 200]; 5 | } 6 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/route-tasks/detail/route.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Route from '@ember/routing/route'; 3 | import { restartableTask, timeout } from 'ember-concurrency'; 4 | 5 | // BEGIN-SNIPPET detail-route 6 | export default class RouteTasksDetailRoute extends Route { 7 | @service notifications; 8 | 9 | setupController(controller, model) { 10 | super.setupController(...arguments); 11 | 12 | this.pollServerForChanges.perform(model.id); 13 | } 14 | 15 | resetController() { 16 | super.resetController(...arguments); 17 | this.pollServerForChanges.cancelAll(); 18 | } 19 | 20 | pollServerForChanges = restartableTask(async (id) => { 21 | let notifications = this.notifications; 22 | await timeout(500); 23 | try { 24 | notifications.info(`Thing ${id}: Starting to poll for changes`); 25 | while (true) { 26 | await timeout(5000); 27 | notifications.info(`Thing ${id}: Polling now...`); 28 | } 29 | } finally { 30 | notifications.warning(`Thing ${id}: No longer polling for changes`); 31 | } 32 | }); 33 | } 34 | // END-SNIPPET 35 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/route-tasks/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class RouteTasksRoute extends Route { 5 | @service router; 6 | 7 | redirect() { 8 | this.router.transitionTo('docs.examples.route-tasks.detail', 1); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/route-tasks/template.hbs: -------------------------------------------------------------------------------- 1 |

    Tasks on Ember.Route (and other long-lived objects)

    2 | 3 |

    4 | ember-concurrency tasks are scoped to the lifetime of 5 | the object they live on, so if that object is destroyed, all of the 6 | tasks attached to it are canceled. This is very convenient when 7 | writing tasks on object with finite lifetimes, like Components, but certain Ember objects, like 8 | Routes (and Controllers), are never actually destroyed. Even 9 | if you can't rely on object destruction to cancel a task, 10 | ember-concurrency makes it easy to run 11 | tasks between lifecycle events other than init 12 | and destroy. 13 |

    14 | 15 |

    Live Example

    16 | 17 |

    18 | Try clicking the links below. As the URL changes, you should see 19 | notifications about the server polling status changing. If you 20 | leave this route (by going to another page on this site), you'll 21 | see that the polling task is being properly canceled. 22 |

    23 | 24 |
      25 | {{#each this.ids as |id|}} 26 |
    • 27 | 28 | Thing {{id}} 29 | 30 |
    • 31 | {{/each}} 32 |
    33 | 34 |
      35 |
    • 36 | setupController kicks off the task with the current model id 37 |
    • 38 |
    • 39 | The pollServerForChanges task polls the server in a loop, 40 | and uses the finally block to notify when it is being canceled. 41 |
    • 42 |
    • 43 | We use restartable to ensure that only one instance of the 44 | task is running at a time, hence any time setupController 45 | performs the task, any prior instances are canceled. 46 |
    • 47 |
    • 48 | We use cancelAll in the route's resetController 49 | hook to make sure the task cancels when the user leaves the route. 50 |
    • 51 |
    52 | 53 | 54 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/examples/task-concurrency/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class TaskConcurrencyRoute extends Route { 5 | @service router; 6 | 7 | redirect() { 8 | this.router.transitionTo('docs.task-concurrency'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/index/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class IndexRoute extends Route { 5 | @service router; 6 | 7 | redirect() { 8 | this.router.transitionTo('docs.introduction'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/installation/template.hbs: -------------------------------------------------------------------------------- 1 |

    Installation

    2 | 3 |

    4 | Within your ember-cli project folder, run the following: 5 |

    6 | 7 | 8 | 9 |

    10 | (Recommended) To use with native JavaScript/TypeScript classes 11 | used throughout this documentation, ensure you also 12 | meet the following in your application/addon: 13 | 14 |

      15 |
    • At least ember-cli-babel@^7.7.2
    • 16 |
    • At least @babel/core@^7.5.0 (as a transitive dependency via ember-cli-babel)
    • 17 |
    18 |

    19 | 20 |

    Configure Babel Transform

    21 | 22 |

    23 | Ember Concurrency requires the use of a Babel Transform to convert tasks in the "async-arrow" notation 24 | (e.g. fooTask = task(async () => { /*...*/ }) into generator functions. Since 25 | Ember Concurrency 4.0.0 (which is an Embroider V2 Addon), this Babel transform needs to be configured 26 | on the consuming application or addon. 27 |

    28 | 29 | 30 | 31 |

    Typescript and Glint

    32 | 33 |

    34 | Typescript and Glint docs for setting up / using 35 | Ember Concurency with TypeScript / Glint. 36 |

    37 | 38 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/introduction/template.hbs: -------------------------------------------------------------------------------- 1 |

    2 | ember-concurrency is an Ember Addon that 3 | makes it easy to write concise, robust, and beautiful 4 | asynchronous code. 5 |

    6 | 7 |

    8 | It provides you with a powerful Task primitive, 9 | which offers the following benefits: 10 |

    11 | 12 |
      13 |
    • 14 | Tasks, unlike Promises, support cancelation. 15 |
    • 16 |
    • 17 | Tasks expose their underlying state (whether they're running or idle) 18 | which makes it trivial to build loading indicators without 19 | having to manually track / mutate state yourself. 20 |
    • 21 |
    • 22 | Task Modifiers make it trivial 23 | to prevent two executions of the same task from running at the same time, e.g. 24 | you can prevent double form submissions using the drop modifier, 25 | or you can configure a task to be restartable so that it starts over 26 | when you click a "Restart" button. Implementing this logic without tasks requires 27 | a lot of boilerplate code and defensive programming. 28 |
    • 29 |
    • 30 | Tasks that live on Components are automatically canceled when that 31 | Component is unrendered; no more if(this.isDestroyed) 32 | checks to prevent timers or ajax responses causing 33 | "set on destroyed object" errors. 34 |
    • 35 |
    36 | 37 |

    Additional Learning Resources

    38 | 39 |

    40 | In addition to the comprehensive documentation on this site, 41 | you might find the following links useful for learning about 42 | ember-concurrency. 43 |

    44 | 45 | 62 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/older-versions/template.hbs: -------------------------------------------------------------------------------- 1 |

    Documentation for Older Versions of Ember Concurrency

    2 | 3 |

    4 | You are reading the documentation for Version 4.x of Ember Concurrency, which 5 | reflects the latest APIs and Best Practices. 6 |

    7 | 8 |

    9 | Documentation for older versions can be found here: 10 |

    11 | 12 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/task-concurrency-advanced/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { task, timeout } from 'ember-concurrency'; 3 | 4 | export default class SharedTasksController extends Controller { 5 | restartableTask3 = task( 6 | this, 7 | { maxConcurrency: 3, restartable: true }, 8 | async (t) => this.sharedTask.perform(t), 9 | ); 10 | 11 | enqueuedTask3 = task({ maxConcurrency: 3, enqueue: true }, async (t) => 12 | this.sharedTask.perform(t), 13 | ); 14 | 15 | droppingTask3 = task({ maxConcurrency: 3, drop: true }, async (t) => 16 | this.sharedTask.perform(t), 17 | ); 18 | 19 | keepLatestTask3 = task( 20 | this, 21 | { maxConcurrency: 3, keepLatest: true }, 22 | async (t) => this.sharedTask.perform(t), 23 | ); 24 | 25 | sharedTask = task(async (tracker) => { 26 | tracker.start(); 27 | try { 28 | // simulate async work 29 | await timeout(1500); 30 | } finally { 31 | tracker.end(); 32 | } 33 | }); 34 | } 35 | 36 | /* 37 | // BEGIN-SNIPPET shared-tasks-concurrent 38 | restartableTask3 = task({ maxConcurrency: 3, restartable: true }, async (t) => { ... } 39 | enqueuedTask3 = task({ maxConcurrency: 3, enqueue: true }, async (t) => { ... } 40 | droppingTask3 = task({ maxConcurrency: 3, drop: true }, async (t) => { ... } 41 | keepLatestTask3 = task({ maxConcurrency: 3, keepLatest: true }, async (t) => { ... } 42 | // END-SNIPPET 43 | */ 44 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/task-concurrency-advanced/template.hbs: -------------------------------------------------------------------------------- 1 |

    Using maxConcurrency: N

    2 | 3 |

    4 | The examples on the previous page limit the concurrency of a task to 1 — only 5 | one instance of a task can run at a time. Most of the time, this 6 | is exactly what you want. 7 |

    8 | 9 |

    10 | There are some cases, however, when you might want to limit 11 | the number of concurrently running task instances to a number greater 12 | than 1. In such cases, you can use the task modifier 13 | maxConcurrency: n to opt into a specific maximum 14 | concurrency other than 1. 15 |

    16 | 17 |

    18 | The examples below use the same task modifiers as the ones on the previous 19 | page, but with maxConcurrency: 3 applied to them: they each 20 | allow 3 running instances before enqueuing, canceling, or dropping 21 | perform()s. 22 |

    23 | 24 | 25 | 26 |

    restartable with maxConcurrency: 3

    27 | 28 |

    29 | When concurrency exceeds maxConcurrency, the oldest running task is canceled. 30 |

    31 | 32 | 33 | 34 |

    35 | 36 | TODO: while restartable is an excellent name when maxConcurrency 37 | is 1, it poorly describes the behavior for values greater than 1. 38 | A better name in this case might be "sliding", as in sliding buffer. 39 | 40 |

    41 | 42 | 43 |

    enqueue with maxConcurrency: 3

    44 | 45 | 46 | 47 |

    drop with maxConcurrency: 3

    48 | 49 | 50 | 51 |

    keepLatest with maxConcurrency: 3

    52 | 53 | 54 | 55 |

    56 | 57 | Thanks to Edward Faulkner for providing 58 | a starting point for the graphs :) 59 | 60 |

    61 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/task-concurrency/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { task, timeout } from 'ember-concurrency'; 3 | 4 | export default class SharedTasksController extends Controller { 5 | defaultTask = task(async (t) => this.sharedTask.perform(t)); 6 | 7 | restartableTask = task({ restartable: true }, async (t) => 8 | this.sharedTask.perform(t), 9 | ); 10 | 11 | enqueuedTask = task({ enqueue: true }, async (t) => 12 | this.sharedTask.perform(t), 13 | ); 14 | 15 | droppingTask = task({ drop: true }, async (t) => this.sharedTask.perform(t)); 16 | 17 | keepLatestTask = task({ keepLatest: true }, async (t) => 18 | this.sharedTask.perform(t), 19 | ); 20 | 21 | sharedTask = task(async (tracker) => { 22 | tracker.start(); 23 | try { 24 | // simulate async work 25 | await timeout(1500); 26 | } finally { 27 | tracker.end(); 28 | } 29 | }); 30 | } 31 | 32 | /* 33 | // BEGIN-SNIPPET shared-tasks 34 | defaultTask = task(async (t) => { ... }); 35 | restartableTask = task({ restartable: true }, async (t) => { ... } 36 | enqueuedTask = task({ enqueue: true }, async (t) => { ... } 37 | droppingTask = task({ drop: true }, async (t) => { ... } 38 | keepLatestTask = task({ keepLatest: true }, async (t) => { ... } 39 | // END-SNIPPET 40 | */ 41 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/task-decorators/template.hbs: -------------------------------------------------------------------------------- 1 |

    Using Task Decorators

    2 | 3 |

    4 | The decorators-based APIs for defining ember-concurrency tasks are no longer recommended 5 | due to decorators not playing nicely with TypeScript-based APIs. 6 |

    7 | 8 |

    9 | If you would like to see the older APIs, please check out 10 | the old docs site. 11 |

    12 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/task-groups/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { task, taskGroup, timeout } from 'ember-concurrency'; 3 | 4 | function* taskFn() { 5 | yield timeout(1500); 6 | } 7 | 8 | // BEGIN-SNIPPET task-groups 9 | export default class TaskGroupsController extends Controller { 10 | @taskGroup({ drop: true }) chores; 11 | 12 | @task({ group: 'chores' }) mowLawn = taskFn; 13 | @task({ group: 'chores' }) doDishes = taskFn; 14 | @task({ group: 'chores' }) changeDiapers = taskFn; 15 | 16 | get tasks() { 17 | return [this.mowLawn, this.doDishes, this.changeDiapers]; 18 | } 19 | } 20 | // END-SNIPPET 21 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/task-groups/template.hbs: -------------------------------------------------------------------------------- 1 |

    Task Groups (deprecated)

    2 | 3 |

    4 | Task Groups are "soft"-deprecated and likely to be removed in a future version 5 | of ember-concurrency (we will release another minor version officially deprecating them 6 | before releasing a major version). Many users found that Task Groups were a bit of an 7 | "over-solution" to a very particular use case, and that they didn't elegantly handle 8 | minor variations to the original stated use case. If you think this deprecation is 9 | made in error, please open an issue. 10 |

    11 | 12 |

    13 | You can read about Task Groups in the 14 | old docs. 15 |

    16 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/task-lifecycle-events/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class TaskLifecycleEventsRoute extends Route { 5 | @service router; 6 | 7 | redirect() { 8 | this.router.transitionTo('docs.advanced.lifecycle-events'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/template.hbs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 | {{#each this.tableOfContents as |entry|}} 6 | {{#if entry.section}} 7 |
    8 | {{entry.section}} 9 |
    10 | {{else}} 11 |
    12 | {{#if entry.route}} 13 | {{entry.title}} 14 | {{else}} 15 | {{entry.title}} 16 | {{/if}} 17 |
    18 | 19 | 20 | {{#if entry.children}} 21 | {{#each entry.children as |child|}} 22 |
    23 | {{child.title}} 24 |
    25 | {{/each}} 26 | {{/if}} 27 | {{/if}} 28 | {{/each}} 29 |
    30 |
    31 |
    32 | 33 | 34 | 35 | {{outlet}} 36 | 37 |



    38 | 39 | 40 |
    41 |
    42 |
    43 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/tutorial/discussion/template.hbs: -------------------------------------------------------------------------------- 1 |

    A Quick Post-Mortem

    2 | 3 |

    4 | In the previous part of the tutorial, we built a component that 5 | fetches and displays nearby retail stores. As you can see, it takes 6 | quite a bit of code to cover all of the corner cases and build 7 | something that is actually production-ready: 8 |

    9 | 10 | 11 | 12 | 13 |

    14 | This is not the beautiful Ember code we all thought we'd be writing, 15 | and unfortunately this kind of code is extremely commonplace. 16 |

    17 | 18 |
    Alternative: Move tricky code to an object with a long lifespan
    19 | 20 |

    21 | Components have limited lifespans: they're rendered, and then 22 | eventually they're unrendered and destroyed. Controllers, Services, Ember-Data 23 | Stores, and Routes, on the other hand, live forever (or at least until 24 | the app is torn down in a testing environment). 25 |

    26 | 27 |

    28 | As such, one approach 29 | to avoiding "set on destroyed object" errors is to move 30 | tricky async logic into a method/action on a Controller or Service that 31 | is invoked by a Component. Sometimes this works, but it's often the case 32 | that even though you no longer see exceptions in the console, you still need to 33 | clean up / stop / cancel some operation on a long lived object in response 34 | to a Component being destroyed. There are Component lifecycle hooks 35 | like willDestroyElement or element modifiers like 36 | will-destroy that you can use for these kinds of things, 37 | but then you still end up with the same amount of code, but now it's smeared 38 | between Component and Controller. 39 |

    40 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/typescript/template.hbs: -------------------------------------------------------------------------------- 1 |

    TypeScript and Glint

    2 | 3 |

    4 | Ember Concurrency tasks play nicely with TypeScript and all of the APIs 5 | covered in these docs. Here is an example of a TypeScript component with an 6 | ember-concurrency task: 7 |

    8 | 9 | 10 | 11 |

    Glint Template Registry

    12 | 13 |

    14 | Ember Concurrency provides a template registry for using the 15 | perform, 16 | cancel-all, and 17 | task 18 | helpers within handlebars templates in Glint "loose mode". See the example 19 | below for how to include Ember Concurrency's template registry in your Glint 20 | configuration. 21 |

    22 | 23 | 24 | 25 |

    Ember Template Imports (.gts/.gts) Files

    26 | 27 |

    28 | Here is an example of a modern .gts file in "strict mode" which imports the 29 | classic 30 | perform 31 | helper from Ember Concurrency. 32 |

    33 | 34 |

    35 | Note: while you can import and use the 36 | perform 37 | helper, it is actually recommended to use the 38 | .perform() 39 | method on each task, which is internally bound to the task (similar to methods 40 | decorated with 41 | @action). One of the benefits of using the 42 | .perform() 43 | method is that it can be used with modern idiomatic patterns like using the 44 | fn 45 | helper to curry additional args when performing the task. 46 |

    47 | 48 |

    49 | Pardon the lack of syntax! PR's welcome to improve our syntax 50 | highlighting! 51 |

    52 | 53 | 54 | 55 |

    Typing Task objects

    56 | 57 |

    58 | In most cases, you don't need to provide type annotations for your task, but 59 | when you do (such as when 60 | 63 | specifying the Args of a Glimmer component), you can use the Task type: 64 |

    65 | 66 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/v4-upgrade/template.hbs: -------------------------------------------------------------------------------- 1 |

    Upgrading to Version 4

    2 | 3 |

    4 | With version 4.0.0, Ember is now an 5 | V2 6 | Embroider Addon. 7 |

    8 | 9 |

    10 | There is one main breaking change with this version: you will need to 11 | register/configure the Babel transform that Ember Concurrency uses to 12 | transform tasks in the "async-arrow" notation (e.g. 13 | fooTask = task(async () => { /*...*/ }) 14 | into generator functions. This will need to be done in any consuming app or 15 | addon that uses the async-arrow task syntax: 16 |

    17 | 18 | -------------------------------------------------------------------------------- /packages/test-app/app/docs/yieldables/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class YieldablesRoute extends Route { 5 | @service router; 6 | 7 | redirect() { 8 | this.router.transitionTo('docs.advanced.yieldables'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers-test/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | import { tracked } from '@glimmer/tracking'; 4 | import { forever, task } from 'ember-concurrency'; 5 | 6 | export default class HelpersTestController extends Controller { 7 | @tracked maybeNullTask = null; 8 | @tracked status = null; 9 | 10 | myTask = task(async (...args) => { 11 | try { 12 | this.status = args.join('-'); 13 | await forever; 14 | } finally { 15 | this.status = 'canceled'; 16 | } 17 | }); 18 | 19 | valueTask = task(async (value) => { 20 | let expected = 'Set value option'; 21 | if (value !== expected) { 22 | throw new Error(`value !== ${expected}`); 23 | } 24 | }); 25 | 26 | returnValue = task(async () => { 27 | return 10; 28 | }); 29 | 30 | someTask = task(async () => { 31 | this.status = 'someTask'; 32 | }); 33 | 34 | @action 35 | setupTask() { 36 | this.maybeNullTask = this.someTask; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers-test/template.hbs: -------------------------------------------------------------------------------- 1 |

    Helpers Test

    2 | 3 |

    {{this.status}}

    4 | 5 | Perform 6 | 7 | Return a Value 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers/caps-bool.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function capsBool([bool] /*, hash*/) { 4 | return bool ? 'YES' : 'no'; 5 | } 6 | 7 | export default helper(capsBool); 8 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers/color.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { htmlSafe } from '@ember/template'; 3 | 4 | export function colorString([color] /*, hash*/) { 5 | return new htmlSafe(`color: ${color};`); 6 | } 7 | 8 | export default helper(colorString); 9 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers/pick-from.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function pickFrom([list, index] /*, hash*/) { 4 | return list[index % list.length]; 5 | } 6 | 7 | export default helper(pickFrom); 8 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers/progress-style.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { htmlSafe } from '@ember/template'; 3 | 4 | export function progressStyleHelper([percent, id, colors] /*, hash*/) { 5 | let color = colors[id % colors.length]; 6 | return new htmlSafe(`width: ${percent}%; background-color: ${color};`); 7 | } 8 | 9 | export default helper(progressStyleHelper); 10 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers/scale.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function scale([value, lowLimit, highLimit] /*, hash*/) { 4 | let v = (100 * value) / (highLimit + 1000 - lowLimit); 5 | 6 | // the 0.001 gets around the annoying fact that {{with falsy}} 7 | // behaves like {{if falsy}} :( 8 | return v + 0.001; 9 | } 10 | 11 | export default helper(scale); 12 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers/subtract.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function subtract([a, b] /*, hash*/) { 4 | return a - b; 5 | } 6 | 7 | export default helper(subtract); 8 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers/sum.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function sum([a, b] /*, hash*/) { 4 | return a + b; 5 | } 6 | 7 | export default helper(sum); 8 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers/swallow-error.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | /** 4 | * This helper is meant to be wrapped around another action that might throw 5 | * an error that we want to suppress. 6 | * 7 | * While this pattern should be used sparingly, as errors should generally not be ignores, it 8 | * is sometimes appropriate to follow do this when working with an Ember Concurrency task that uses 9 | * the `.error` derived state to directly render the error from a Task. In these cases, we rarely 10 | * want the error to bubble up to the application itself, as we're already handling the error case. 11 | * 12 | * ```hbs 13 | * 16 | * ``` 17 | */ 18 | export function swallowError([fn]) { 19 | return function callAndSwallowError(...args) { 20 | try { 21 | const response = fn(...args); 22 | 23 | if (response.catch) { 24 | return response.catch(function () { 25 | // Swallow async error 26 | }); 27 | } 28 | 29 | return response; 30 | } catch (e) { 31 | // Swallow synchronous error 32 | } 33 | }; 34 | } 35 | 36 | export default helper(swallowError); 37 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers/width.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function computeWidth([start, end, upper] /*, hash*/) { 4 | return end === Infinity ? upper - start : end - start; 5 | } 6 | 7 | export default helper(computeWidth); 8 | -------------------------------------------------------------------------------- /packages/test-app/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ember-concurrency 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 18 | 19 | {{content-for "head-footer"}} 20 | 21 | 22 | {{content-for "body"}} 23 | 24 | 25 | 26 | 27 | {{content-for "body-footer"}} 28 | 29 | 30 | -------------------------------------------------------------------------------- /packages/test-app/app/index/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class IndexRoute extends Route { 5 | @service router; 6 | 7 | redirect() { 8 | this.router.transitionTo('docs'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-app/app/modifiers/autofocus.js: -------------------------------------------------------------------------------- 1 | import { modifier } from 'ember-modifier'; 2 | 3 | export default modifier(function autofocus(element) { 4 | const childElement = element.querySelector('input'); 5 | childElement.focus(); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/test-app/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'test-app/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () { 10 | this.route('docs', function () { 11 | this.route('introduction'); 12 | this.route('v4-upgrade'); 13 | this.route('older-versions'); 14 | this.route('installation'); 15 | 16 | this.route('tutorial', function () { 17 | this.route('discussion'); 18 | this.route('refactor'); 19 | }); 20 | 21 | this.route('task-function-syntax'); 22 | this.route('task-decorators'); 23 | this.route('task-concurrency'); 24 | this.route('task-concurrency-advanced'); 25 | 26 | this.route('cancelation'); 27 | this.route('error-vs-cancelation'); 28 | this.route('child-tasks'); 29 | this.route('task-groups'); 30 | this.route('derived-state'); 31 | this.route('events'); 32 | this.route('testing-debugging'); 33 | this.route('typescript'); 34 | this.route('faq'); 35 | 36 | this.route('advanced', function () { 37 | this.route('encapsulated-task'); 38 | this.route('lifecycle-events'); 39 | this.route('task-modifiers'); 40 | this.route('yieldables'); 41 | }); 42 | 43 | this.route('examples', function () { 44 | this.route('increment-buttons'); 45 | this.route('loading-ui'); 46 | this.route('autocomplete'); 47 | this.route('task-concurrency'); 48 | this.route('ajax-throttling'); 49 | this.route('route-tasks', function () { 50 | this.route('detail', { path: ':id' }); 51 | }); 52 | this.route('joining-tasks'); 53 | }); 54 | this.route('task-cancelation-help'); 55 | this.route('404', { path: '*path' }); 56 | }); 57 | this.route('helpers-test'); 58 | this.route('deprecation-test'); 59 | this.route('testing-ergo', function () { 60 | this.route('foo'); 61 | this.route('foo-settimeout'); 62 | this.route('slow'); 63 | this.route('timer-loop'); 64 | }); 65 | this.route('task-injection-test'); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/test-app/app/services/fun.js: -------------------------------------------------------------------------------- 1 | import Service from '@ember/service'; 2 | 3 | export default class FunService extends Service { 4 | foo = 123; 5 | } 6 | -------------------------------------------------------------------------------- /packages/test-app/app/services/notifications.js: -------------------------------------------------------------------------------- 1 | import Service from '@ember/service'; 2 | import { A } from '@ember/array'; 3 | import { later } from '@ember/runloop'; 4 | 5 | export default class NotificationsService extends Service { 6 | messages = A(); 7 | 8 | notify(severity, message) { 9 | const logObject = { severity, message }; 10 | this.messages.pushObject(logObject); 11 | 12 | later(() => { 13 | this.messages.removeObject(logObject); 14 | }, 8000); 15 | } 16 | 17 | info(message) { 18 | this.notify('info', message); 19 | } 20 | 21 | warning(message) { 22 | this.notify('warning', message); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/test-app/app/task-injection-test/controller.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Controller from '@ember/controller'; 3 | import { task } from 'ember-concurrency'; 4 | 5 | export default Controller.extend({ 6 | users: null, 7 | 8 | myTask: task({ 9 | fun: service(), 10 | *perform(suffix) { 11 | let value = yield this.subtask.perform(suffix); 12 | return `${this.fun.foo}-${value}`; 13 | }, 14 | 15 | subtask: task({ 16 | fun: service(), 17 | wat: 2, 18 | *perform(suffix) { 19 | if (suffix) { 20 | return suffix; 21 | } else { 22 | return this.fun.foo * this.wat; 23 | } 24 | }, 25 | }), 26 | }), 27 | }); 28 | -------------------------------------------------------------------------------- /packages/test-app/app/task-injection-test/template.hbs: -------------------------------------------------------------------------------- 1 |

    Task injection test

    2 | 3 | 6 | 7 | 10 | 11 |

    {{this.myTask.last.value}}

    12 | -------------------------------------------------------------------------------- /packages/test-app/app/task-modifiers/benchmark.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET task-modifier-benchmark 2 | // app/task-modifiers/benchmark.js 3 | import { registerModifier } from 'ember-concurrency'; 4 | 5 | function benchmarkModifier(taskFactory, option) { 6 | if (!window && !window.performance) { 7 | return; 8 | } 9 | 10 | if (option) { 11 | let taskDefinition = taskFactory.taskDefinition; 12 | let benchmarkedDefinition = function* (...args) { 13 | let taskName = taskFactory.name; 14 | let namespace = `ember-concurrency.${taskName}`; 15 | window.performance.mark(`${namespace}.start`); 16 | 17 | try { 18 | yield* taskDefinition(...args); 19 | window.performance.measure( 20 | `${namespace}.success`, 21 | `${namespace}.start`, 22 | ); 23 | } catch (e) { 24 | window.performance.measure(`${namespace}.error`, `${namespace}.start`); 25 | throw e; 26 | } finally { 27 | window.performance.measure( 28 | `${namespace}.runtime`, 29 | `${namespace}.start`, 30 | ); 31 | } 32 | }; 33 | 34 | taskFactory.setTaskDefinition(benchmarkedDefinition); 35 | } 36 | } 37 | 38 | registerModifier('benchmark', benchmarkModifier); 39 | 40 | export default benchmarkModifier; 41 | // END-SNIPPET 42 | -------------------------------------------------------------------------------- /packages/test-app/app/templates/components/my-button.hbs: -------------------------------------------------------------------------------- 1 | {{!- FIXME: I fail to render under embroider-safe when in pod structure --}} 2 | 5 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/foo-settimeout/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { task, rawTimeout } from 'ember-concurrency'; 3 | 4 | export default Controller.extend({ 5 | isShowingButton: false, 6 | showButtonSoon: task(function* () { 7 | this.set('isShowingButton', false); 8 | yield rawTimeout(500); 9 | this.set('isShowingButton', true); 10 | }), 11 | }); 12 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/foo-settimeout/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | setupController(controller) { 5 | controller.showButtonSoon.perform(); 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/foo-settimeout/template.hbs: -------------------------------------------------------------------------------- 1 | {{#if this.isShowingButton}} 2 |
    Eventual Button
    3 | {{/if}} 4 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/foo/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | import { tracked } from '@glimmer/tracking'; 4 | import { task, timeout } from 'ember-concurrency'; 5 | 6 | export default class FooController extends Controller { 7 | @tracked isShowingButton = false; 8 | 9 | showButtonSoon = task(async () => { 10 | this.isShowingButton = false; 11 | await timeout(200); 12 | this.isShowingButton = true; 13 | }); 14 | 15 | value = 0; 16 | 17 | @action 18 | setValue() { 19 | this.value = 123; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/foo/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | setupController(controller) { 5 | controller.showButtonSoon.perform(); 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/foo/template.hbs: -------------------------------------------------------------------------------- 1 | {{#if this.isShowingButton}} 2 |
    Eventual Button
    3 |
    value={{this.value}}
    4 | {{else}} 5 |
    Disappearing Content
    6 | {{/if}} 7 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/loading/template.hbs: -------------------------------------------------------------------------------- 1 |
    I am a loading route.
    2 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/slow/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { timeout } from 'ember-concurrency'; 3 | 4 | export default Route.extend({ 5 | model() { 6 | return timeout(200).then(() => {}); 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/slow/template.hbs: -------------------------------------------------------------------------------- 1 |
    Welcome to slow route.
    2 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/timer-loop/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default Controller.extend({ 4 | foo: 0, 5 | }); 6 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/timer-loop/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { task, timeout } from 'ember-concurrency'; 3 | 4 | export default Route.extend({ 5 | setupController() { 6 | this.loopingTask.perform(); 7 | }, 8 | loopingTask: task(function* () { 9 | while (true) { 10 | // eslint-disable-next-line ember/no-controller-access-in-routes 11 | this.controller.incrementProperty('foo'); 12 | yield timeout(200); 13 | } 14 | }), 15 | }); 16 | -------------------------------------------------------------------------------- /packages/test-app/app/testing-ergo/timer-loop/template.hbs: -------------------------------------------------------------------------------- 1 |

    foo={{this.foo}}

    2 | -------------------------------------------------------------------------------- /packages/test-app/app/utils.js: -------------------------------------------------------------------------------- 1 | const WORDS = ['ember', 'tomster', 'swag', 'yolo', 'turbo', 'ajax']; 2 | export function randomWord() { 3 | return WORDS[Math.floor(Math.random() * WORDS.length)]; 4 | } 5 | -------------------------------------------------------------------------------- /packages/test-app/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "4.11.0", 7 | "blueprints": [ 8 | { 9 | "name": "addon", 10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output", 11 | "codemodsSource": "ember-addon-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": ["--yarn", "--no-welcome"] 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/test-app/config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // eslint-disable-next-line n/no-missing-require 4 | const getChannelURL = require('ember-source-channel-url'); 5 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 6 | 7 | module.exports = async function () { 8 | return { 9 | usePnpm: true, 10 | scenarios: [ 11 | { 12 | name: 'ember-lts-3.28', 13 | npm: { 14 | devDependencies: { 15 | 'ember-source': '~3.28.0', 16 | }, 17 | }, 18 | }, 19 | { 20 | name: 'ember-lts-4.4', 21 | npm: { 22 | devDependencies: { 23 | 'ember-source': '~4.4.0', 24 | }, 25 | }, 26 | }, 27 | { 28 | name: 'ember-lts-4.8', 29 | npm: { 30 | devDependencies: { 31 | 'ember-source': '~4.8.0', 32 | }, 33 | }, 34 | }, 35 | { 36 | name: 'ember-lts-4.12', 37 | npm: { 38 | devDependencies: { 39 | 'ember-source': '~4.12.0', 40 | }, 41 | }, 42 | }, 43 | { 44 | name: 'ember-5.0', 45 | npm: { 46 | devDependencies: { 47 | 'ember-source': '~5.0.0', 48 | }, 49 | }, 50 | }, 51 | { 52 | name: 'ember-release', 53 | npm: { 54 | devDependencies: { 55 | 'ember-source': await getChannelURL('release'), 56 | }, 57 | }, 58 | }, 59 | { 60 | name: 'ember-beta', 61 | npm: { 62 | devDependencies: { 63 | 'ember-source': await getChannelURL('beta'), 64 | }, 65 | }, 66 | }, 67 | { 68 | name: 'ember-canary', 69 | npm: { 70 | devDependencies: { 71 | 'ember-source': await getChannelURL('canary'), 72 | }, 73 | }, 74 | }, 75 | { 76 | name: 'ember-default', 77 | npm: { 78 | devDependencies: {}, 79 | }, 80 | }, 81 | { 82 | name: 'tsc', 83 | command: 'tsc', 84 | }, 85 | embroiderSafe(), 86 | embroiderOptimized(), 87 | ], 88 | }; 89 | }; 90 | -------------------------------------------------------------------------------- /packages/test-app/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'test-app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | EXTEND_PROTOTYPES: false, 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 14 | }, 15 | }, 16 | 17 | APP: { 18 | // Here you can pass flags/options to your application instance 19 | // when it is created 20 | }, 21 | 22 | emberConcurrencyVersion: require('../../ember-concurrency/package.json') 23 | .version, 24 | 25 | fastboot: { 26 | hostWhitelist: [/^localhost:\d+$/, 'ember-concurrency.com'], 27 | }, 28 | }; 29 | 30 | if (environment === 'development') { 31 | // ENV.APP.LOG_RESOLVER = true; 32 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 33 | // ENV.APP.LOG_TRANSITIONS = true; 34 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 35 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 36 | } 37 | 38 | if (environment === 'test') { 39 | // Testem prefers this... 40 | ENV.locationType = 'none'; 41 | 42 | // keep test console output quieter 43 | ENV.APP.LOG_ACTIVE_GENERATION = false; 44 | ENV.APP.LOG_VIEW_LOOKUPS = false; 45 | 46 | ENV.APP.rootElement = '#ember-testing'; 47 | ENV.APP.autoboot = false; 48 | } 49 | 50 | if (environment === 'production') { 51 | // here you can enable a production-specific feature 52 | } 53 | 54 | return ENV; 55 | }; 56 | -------------------------------------------------------------------------------- /packages/test-app/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /packages/test-app/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 2 Chrome versions', 5 | 'last 2 Firefox versions', 6 | 'last 2 Safari versions', 7 | 8 | // needed for running fastboot locally 9 | 'node 12.0', 10 | ]; 11 | 12 | module.exports = { 13 | browsers, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/test-app/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | const { maybeEmbroider } = require('@embroider/test-setup'); 5 | const urls = require('./lib/prember-urls'); 6 | 7 | module.exports = function (defaults) { 8 | const app = new EmberApp(defaults, { 9 | minifyJS: { 10 | enabled: false, 11 | }, 12 | 13 | snippetPaths: ['snippets'], 14 | snippetSearchPaths: ['app'], 15 | 16 | 'ember-prism': { 17 | components: ['javascript', 'typescript', 'bash', 'markup'], 18 | }, 19 | 20 | 'ember-cli-babel': { 21 | enableTypeScriptTransform: true, 22 | }, 23 | 24 | emberCliFontAwesome: { 25 | useScss: true, 26 | }, 27 | 28 | autoImport: { 29 | forbidEval: true, 30 | webpack: { 31 | // Webpack won't auto-detect, because of "maintained node versions" in config/targets.js 32 | target: 'web', 33 | }, 34 | }, 35 | 36 | prember: { 37 | urls, 38 | // GitHub Pages uses this filename to serve 404s 39 | emptyFile: '404.html', 40 | }, 41 | 42 | babel: { 43 | plugins: [ 44 | require.resolve('ember-concurrency/async-arrow-task-transform'), 45 | ], 46 | }, 47 | }); 48 | 49 | /* 50 | This build file specifies the options for the test app of this addon. 51 | This build file does *not* influence how the addon or the app using it 52 | behave. You most likely want to be modifying `./index.js` or app's build file 53 | */ 54 | 55 | return maybeEmbroider(app, { 56 | packageRules: [ 57 | { 58 | package: 'test-app', 59 | components: { 60 | '{{e-c-test}}': { 61 | safeToIgnore: true, 62 | }, 63 | '{{inner-component}}': { 64 | safeToIgnore: true, 65 | }, 66 | '{{my-component}}': { 67 | safeToIgnore: true, 68 | }, 69 | '{{test-swallow-error}}': { 70 | safeToIgnore: true, 71 | }, 72 | '{{test-async-arrow-task}}': { 73 | safeToIgnore: true, 74 | }, 75 | }, 76 | }, 77 | ], 78 | skipBabel: [ 79 | { 80 | package: 'qunit', 81 | }, 82 | ], 83 | }); 84 | }; 85 | -------------------------------------------------------------------------------- /packages/test-app/lib/prember-urls.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | const jsdom = require('jsdom'); 3 | const { JSDOM } = jsdom; 4 | 5 | // eslint-disable-next-line no-unused-vars 6 | module.exports = function urls({ distDir, visit }) { 7 | return visit('/docs/introduction') 8 | .then((page) => { 9 | return page.html(); 10 | }) 11 | .then((html) => { 12 | let dom = new JSDOM(html); 13 | let urls = ['/']; 14 | for (let aTag of [ 15 | ...dom.window.document.querySelectorAll('.side-menu a'), 16 | ]) { 17 | if (aTag.href) { 18 | urls.push(aTag.href); 19 | } 20 | } 21 | return urls; 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /packages/test-app/public/CNAME: -------------------------------------------------------------------------------- 1 | ember-concurrency.com 2 | -------------------------------------------------------------------------------- /packages/test-app/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /packages/test-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/test-app/snippets/babel-transform-config.js: -------------------------------------------------------------------------------- 1 | // in app ember-cli-build.js 2 | 3 | const app = new EmberApp(defaults, { 4 | // ... 5 | babel: { 6 | plugins: [ 7 | // ... any other plugins 8 | require.resolve("ember-concurrency/async-arrow-task-transform"), 9 | 10 | // NOTE: put any code coverage plugins last, after the transform. 11 | ], 12 | } 13 | }); 14 | 15 | // in V1 addon index.js 16 | 17 | // ... 18 | options: { 19 | babel: { 20 | plugins: [ 21 | require.resolve('ember-concurrency/async-arrow-task-transform'), 22 | ], 23 | }, 24 | }, 25 | 26 | // in V2 addon babel.config.json 27 | 28 | { 29 | "plugins": [ 30 | [ 31 | // ... any other plugins 32 | "ember-concurrency/async-arrow-task-transform" 33 | ] 34 | } 35 | 36 | // in engine index.js 37 | 38 | // ... 39 | babel: { 40 | plugins: [ 41 | require.resolve('ember-concurrency/async-arrow-task-transform'), 42 | ], 43 | }, 44 | -------------------------------------------------------------------------------- /packages/test-app/snippets/ember-install.sh: -------------------------------------------------------------------------------- 1 | ember install ember-concurrency 2 | -------------------------------------------------------------------------------- /packages/test-app/snippets/encapsulated-task.js: -------------------------------------------------------------------------------- 1 | import { task } from 'ember-concurrency'; 2 | 3 | export default class EncapsulatedTaskComponent extends Component { 4 | outerFoo = 123; 5 | 6 | regularTask = task(async value => { 7 | // this is a classic/regular ember-concurrency task, 8 | // which has direct access to the host object that it 9 | // lives on via `this` 10 | console.log(this.outerFoo); // => 123 11 | await doSomeAsync(); 12 | this.set('outerFoo', value); 13 | }); 14 | 15 | @task encapsulatedTask = { 16 | innerFoo: 456, 17 | 18 | // this `*perform() {}` syntax is valid JavaScript shorthand 19 | // syntax for `perform: function * () {}` 20 | 21 | *perform(value) { 22 | // this is an encapulated task. It does NOT have 23 | // direct access to the host object it lives on, but rather 24 | // only the properties defined within the POJO passed 25 | // to the `task()` constructor. 26 | console.log(this.innerFoo); // => 456 27 | 28 | // `this` is the currently executing TaskInstance, so 29 | // you can also get classic TaskInstance properties 30 | // provided by ember-concurrency. 31 | console.log(this.isRunning); // => true 32 | 33 | yield doSomeAsync(); 34 | this.set('innerFoo', value); 35 | }, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/test-app/snippets/last-value-decorator.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { task } from 'ember-concurrency'; 3 | import { lastValue } from 'ember-concurrency'; 4 | 5 | export default class ExampleComponent extends Component { 6 | someTask = task(async () => { 7 | // ... 8 | }); 9 | 10 | @lastValue('someTask') 11 | someTaskValue; 12 | 13 | @lastValue('someTask') 14 | someTaskValueWithDefault = 'A default value'; 15 | } 16 | -------------------------------------------------------------------------------- /packages/test-app/snippets/poll-loop-break-1.js: -------------------------------------------------------------------------------- 1 | pollForChanges = task(async () => { 2 | while(true) { 3 | await pollServerForChanges(); 4 | if (Ember.testing) { return; } 5 | await timeout(5000); 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /packages/test-app/snippets/poll-loop-classic.js: -------------------------------------------------------------------------------- 1 | async pollForChanges() { 2 | if (this.isDestroyed) { return; } 3 | await pollServerForChanges(); 4 | run.later(this, 'pollForChanges', 5000); 5 | } 6 | -------------------------------------------------------------------------------- /packages/test-app/snippets/poll-loop.js: -------------------------------------------------------------------------------- 1 | pollForChanges = task(async () => { 2 | while(true) { 3 | yield pollServerForChanges(); 4 | yield timeout(5000); 5 | } 6 | }) 7 | -------------------------------------------------------------------------------- /packages/test-app/snippets/task-cancelation-example-1.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { action } from '@ember/object'; 3 | import { tracked } from '@glimmer/tracking'; 4 | import { task, timeout } from 'ember-concurrency'; 5 | 6 | export default class TaskCancelationExampleComponent extends Component { 7 | @tracked results = null; 8 | 9 | queryServer = task(async () => { 10 | await timeout(10000); 11 | return 123; 12 | }); 13 | 14 | @action 15 | async fetchResults() { 16 | let results = await this.queryServer.perform(); 17 | this.results = results; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/test-app/snippets/task-cancelation-example-2.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { action } from '@ember/object'; 3 | import { tracked } from '@glimmer/tracking'; 4 | import { didCancel, task, timeout } from 'ember-concurrency'; 5 | 6 | export default class TaskCancelationExampleComponent extends Component { 7 | @tracked results = null; 8 | 9 | queryServer = task(async () => { 10 | await timeout(10000); 11 | return 123; 12 | }); 13 | 14 | @action 15 | async fetchResults() { 16 | try { 17 | let results = await this.queryServer.perform(); 18 | this.results = results; 19 | } catch (e) { 20 | if (!didCancel(e)) { 21 | // re-throw the non-cancelation error 22 | throw e; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/test-app/snippets/task-cancelation-example-3.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { task, timeout } from 'ember-concurrency'; 4 | 5 | export default class TaskCancelationExampleComponent extends Component { 6 | @tracked results = null; 7 | 8 | queryServer = task(async () => { 9 | await timeout(10000); 10 | return 123; 11 | }); 12 | 13 | fetchResults = task(async () => { 14 | let results = await this.queryServer.perform(); 15 | this.results = results; 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /packages/test-app/snippets/task-decorators-1.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { task } from 'ember-concurrency'; 3 | 4 | export default class ExampleComponent extends Component { 5 | doStuff = task(async () => { 6 | // ... 7 | }); 8 | 9 | // and then elsewhere 10 | executeTheTask() { 11 | // `doStuff` is still a `Task` object that can be `.perform()`ed 12 | this.doStuff.perform(); 13 | console.log(this.doStuff.isRunning); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/test-app/snippets/task-decorators-2.js: -------------------------------------------------------------------------------- 1 | @task({ 2 | maxConcurrency: 3, 3 | restartable: true 4 | }) 5 | *doStuff() { 6 | // ... 7 | } 8 | -------------------------------------------------------------------------------- /packages/test-app/snippets/task-decorators-3.js: -------------------------------------------------------------------------------- 1 | @task({ on: 'didInsertElement' }) 2 | *doStuff() { 3 | // ... 4 | } 5 | -------------------------------------------------------------------------------- /packages/test-app/snippets/task-decorators-4.js: -------------------------------------------------------------------------------- 1 | @restartableTask({ maxConcurrency: 3 }) 2 | *doStuff() { 3 | // ... 4 | } 5 | -------------------------------------------------------------------------------- /packages/test-app/snippets/task-decorators-5.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { task } from 'ember-concurrency'; 3 | 4 | export default class ExampleComponent extends Component { 5 | @task 6 | doStuff = { 7 | privateState: 123, 8 | *perform() { 9 | // ... 10 | } 11 | }; 12 | 13 | // and then elsewhere 14 | executeTheTask() { 15 | // `doStuff` is still a `Task` object that can be `.perform()`ed 16 | this.doStuff.perform(); 17 | console.log(this.doStuff.isRunning); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/test-app/snippets/task-group-decorators-1.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { task, taskGroup } from 'ember-concurrency'; 3 | 4 | export default class ExampleComponent extends Component { 5 | @taskGroup 6 | someTaskGroup; 7 | 8 | doStuff = task({ group: 'someTaskGroup' }, async () => { 9 | // ... 10 | }); 11 | 12 | doOtherStuff = task({ group: 'someTaskGroup' }, async () => { 13 | // ... 14 | }); 15 | 16 | // and then elsewhere 17 | executeTheTask() { 18 | // `doStuff` is still a `Task `object that can be `.perform()`ed 19 | this.doStuff.perform(); 20 | 21 | // `someTaskGroup` is still a `TaskGroup` object 22 | console.log(this.someTaskGroup.isRunning); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/test-app/snippets/task-group-decorators-2.js: -------------------------------------------------------------------------------- 1 | @taskGroup({ 2 | maxConcurrency: 3, 3 | drop: true 4 | }) someTaskGroup; 5 | -------------------------------------------------------------------------------- /packages/test-app/snippets/task-group-decorators-3.js: -------------------------------------------------------------------------------- 1 | @dropTaskGroup({ maxConcurrency: 3 }) someTaskGroup; 2 | -------------------------------------------------------------------------------- /packages/test-app/snippets/ts/basic-example.ts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { task, timeout } from 'ember-concurrency'; 3 | 4 | export default class extends Component { 5 | myTask = task(async (ms: number) => { 6 | await timeout(ms); 7 | return 'done!'; 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/test-app/snippets/ts/template-import-example.txt: -------------------------------------------------------------------------------- 1 | import Component from "@glimmer/component"; 2 | import { task } from "ember-concurrency"; 3 | import perform from "ember-concurrency/helpers/perform"; 4 | import { on } from "@ember/modifier"; 5 | import { fn } from "@ember/helper"; 6 | 7 | export default class Demo extends Component { 8 | taskNoArgs = task(async () => { 9 | console.log("Look ma, no args!"); 10 | }); 11 | 12 | taskWithArgs = task(async (value: string) => { 13 | console.log(value); 14 | }); 15 | 16 | 33 | } 34 | -------------------------------------------------------------------------------- /packages/test-app/snippets/ts/template-registry-example.ts: -------------------------------------------------------------------------------- 1 | // e.g. types/glint.d.ts 2 | import '@glint/environment-ember-loose'; 3 | import type EmberConcurrencyRegistry from 'ember-concurrency/template-registry'; 4 | 5 | declare module '@glint/environment-ember-loose/registry' { 6 | export default interface Registry 7 | extends EmberConcurrencyRegistry /* other addon registries */ { 8 | // local entries 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-app/snippets/ts/typing-task.ts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { task, timeout } from 'ember-concurrency'; 3 | import type { Task } from 'ember-concurrency'; 4 | 5 | // Define a Type task that takes a single number argument and returns a string 6 | type MyTaskType = Task; 7 | 8 | interface Args { 9 | fooTask: MyTaskType; 10 | } 11 | 12 | export default class extends Component { 13 | slowlyComputeStringLength: MyTaskType = task(async (ms: number) => { 14 | await timeout(ms); 15 | 16 | const length = await this.args.fooTask.perform(ms); 17 | 18 | return length; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages/test-app/snippets/yieldable-req-idle-cb-task.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { task } from 'ember-concurrency'; 3 | import idleCallback from 'my-app/yieldables/idle-callback'; 4 | 5 | export class MyComponent extends Component { 6 | backgroundTask = task(async () => { 7 | while (1) { 8 | await idleCallback(); 9 | 10 | const data = this.complicatedNumberCrunching(); 11 | await this.sendData(data); 12 | } 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/test-app/snippets/yieldable-req-idle-cb.js: -------------------------------------------------------------------------------- 1 | // app/yieldables/idle-callback.js 2 | import { Yieldable } from 'ember-concurrency'; 3 | 4 | class IdleCallbackYieldable extends Yieldable { 5 | onYield(state) { 6 | let callbackId = requestIdleCallback(() => state.next()); 7 | 8 | return () => cancelIdleCallback(callbackId); 9 | } 10 | } 11 | 12 | export const idleCallback = () => new IdleCallbackYieldable(); 13 | 14 | export default idleCallback; 15 | -------------------------------------------------------------------------------- /packages/test-app/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome', 'Firefox'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | Firefox: { 23 | mode: 'ci', 24 | args: ['-headless'], 25 | }, 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /packages/test-app/tests/acceptance/helpers-test.js: -------------------------------------------------------------------------------- 1 | import { click, visit, currentURL } from '@ember/test-helpers'; 2 | import { setupApplicationTest } from 'ember-qunit'; 3 | import { module, test } from 'qunit'; 4 | import { setDebugFunction, getDebugFunction } from '@ember/debug'; 5 | 6 | const originalAssert = getDebugFunction('assert'); 7 | 8 | module('Acceptance | helpers', function (hooks) { 9 | setupApplicationTest(hooks); 10 | 11 | hooks.afterEach(function () { 12 | setDebugFunction('assert', originalAssert); 13 | }); 14 | 15 | test('perform and cancel-all', async function (assert) { 16 | assert.expect(3); 17 | await visit('/helpers-test'); 18 | assert.strictEqual(currentURL(), '/helpers-test'); 19 | 20 | await click('.perform-task'); 21 | assert.dom('.task-status').hasText('1-2-3-4'); 22 | await click('.cancel-task'); 23 | assert.dom('.task-status').hasText('canceled'); 24 | }); 25 | 26 | test('setting value="..." should behave like closure actions and rewrite event arg', async function (assert) { 27 | assert.expect(0); 28 | await visit('/helpers-test'); 29 | await click('.set-value-option-task'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/test-app/tests/acceptance/root-test.js: -------------------------------------------------------------------------------- 1 | import { module, test, skip } from 'qunit'; 2 | import { setupApplicationTest } from 'ember-qunit'; 3 | import { visit } from '@ember/test-helpers'; 4 | import { run, _cancelTimers } from '@ember/runloop'; 5 | import { FLATTENED_TABLE_OF_CONTENTS } from 'test-app/docs/controller'; 6 | 7 | const cancelTimers = _cancelTimers || run.cancelTimers; 8 | 9 | module('Acceptance | root', function (hooks) { 10 | setupApplicationTest(hooks); 11 | 12 | FLATTENED_TABLE_OF_CONTENTS.forEach((page) => { 13 | if (!page.route) { 14 | return; 15 | } 16 | let testMethod = page.skipTest ? skip : test; 17 | testMethod(`visiting ${page.route}`, async function (assert) { 18 | assert.expect(0); 19 | let url = page.route.replace(/\./g, '/'); 20 | 21 | visit(url); 22 | await new Promise((r) => { 23 | setTimeout(() => { 24 | cancelTimers(); 25 | r(); 26 | }, 200); 27 | }); 28 | cancelTimers(); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/test-app/tests/acceptance/task-injection-test.js: -------------------------------------------------------------------------------- 1 | import { click, visit } from '@ember/test-helpers'; 2 | import { setupApplicationTest } from 'ember-qunit'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Acceptance | injections on encapsulated tests', function (hooks) { 6 | setupApplicationTest(hooks); 7 | 8 | test('encapsulated tasks support injections', async function (assert) { 9 | assert.expect(2); 10 | 11 | await visit('/task-injection-test'); 12 | 13 | await click(`[data-test-selector="perform-task-w-injection-button"]`); 14 | assert.dom(`[data-test-selector="perform-task-result"]`).hasText('123-246'); 15 | 16 | await click( 17 | `[data-test-selector="perform-task-w-injection-button-part-2"]`, 18 | ); 19 | assert.dom(`[data-test-selector="perform-task-result"]`).hasText('123-456'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/test-app/tests/helpers/helpers.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export function makeAsyncError(hooks) { 4 | hooks.afterEach(() => (Ember.onerror = null)); 5 | return () => new window.Promise((r) => (Ember.onerror = r)); 6 | } 7 | -------------------------------------------------------------------------------- /packages/test-app/tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | } from 'ember-qunit'; 6 | 7 | // This file exists to provide wrappers around ember-qunit's / ember-mocha's 8 | // test setup functions. This way, you can easily extend the setup that is 9 | // needed per test type. 10 | 11 | function setupApplicationTest(hooks, options) { 12 | upstreamSetupApplicationTest(hooks, options); 13 | 14 | // Additional setup for application tests can be done here. 15 | // 16 | // For example, if you need an authenticated session for each 17 | // application test, you could do: 18 | // 19 | // hooks.beforeEach(async function () { 20 | // await authenticateSession(); // ember-simple-auth 21 | // }); 22 | // 23 | // This is also a good place to call test setup functions coming 24 | // from other addons: 25 | // 26 | // setupIntl(hooks); // ember-intl 27 | // setupMirage(hooks); // ember-cli-mirage 28 | } 29 | 30 | function setupRenderingTest(hooks, options) { 31 | upstreamSetupRenderingTest(hooks, options); 32 | 33 | // Additional setup for rendering tests can be done here. 34 | } 35 | 36 | function setupTest(hooks, options) { 37 | upstreamSetupTest(hooks, options); 38 | 39 | // Additional setup for unit tests can be done here. 40 | } 41 | 42 | export { setupApplicationTest, setupRenderingTest, setupTest }; 43 | -------------------------------------------------------------------------------- /packages/test-app/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tests 6 | 7 | 8 | 9 | {{content-for "head"}} {{content-for "test-head"}} 10 | 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} {{content-for "test-head-footer"}} 16 | 17 | 18 | {{content-for "body"}} {{content-for "test-body"}} 19 | 20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{content-for "body-footer"}} {{content-for "test-body-footer"}} 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/test-app/tests/integration/helpers/task-action-test.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | import hbs from 'htmlbars-inline-precompile'; 6 | import { task } from 'ember-concurrency'; 7 | import { click } from '@ember/test-helpers'; 8 | 9 | module('Integration | Helper | task action', function (hooks) { 10 | setupRenderingTest(hooks); 11 | 12 | test('task produces a curried version of the task passed into it', async function (assert) { 13 | assert.expect(2); 14 | 15 | this.owner.register( 16 | 'component:my-component', 17 | Component.extend({ 18 | myTask: task(function* (...args) { 19 | assert.deepEqual(args, [1, 2, 3, 4, 5, 6]); 20 | return 999; 21 | }), 22 | }), 23 | ); 24 | 25 | this.owner.register( 26 | 'component:inner-component', 27 | Component.extend({ 28 | click() { 29 | return this.curriedTask.perform(4, 5, 6).then((v) => { 30 | assert.strictEqual(v, 999); 31 | }); 32 | }, 33 | }), 34 | ); 35 | 36 | this.owner.register( 37 | 'template:components/my-component', 38 | hbs``, 42 | ); 43 | 44 | await render(hbs``); 45 | 46 | await click('#my-component'); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/test-app/tests/integration/no-render-breaking-test.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | import hbs from 'htmlbars-inline-precompile'; 6 | import { task, timeout } from 'ember-concurrency'; 7 | 8 | module('Integration | no render breaking', function (hooks) { 9 | setupRenderingTest(hooks); 10 | 11 | test('Issue #337 | internal task state updates do not trigger re-render assertions w/ auto-tracking', async function (assert) { 12 | assert.expect(1); 13 | 14 | this.owner.register( 15 | 'component:e-c-test', 16 | Component.extend({ 17 | layout: hbs``, 18 | 19 | focusIn() { 20 | this.exampleTask.perform(); 21 | }, 22 | 23 | exampleTask: task(function* () { 24 | yield timeout(100); 25 | }), 26 | }), 27 | ); 28 | 29 | await render(hbs``); 30 | assert.ok(true, 'Renders'); 31 | }); 32 | 33 | test('Issue #340 | internal task state updates in cancellation do not trigger re-render assertions w/ auto-tracking', async function (assert) { 34 | assert.expect(1); 35 | 36 | this.owner.register( 37 | 'component:e-c-test', 38 | Component.extend({ 39 | layout: hbs`
    {{this.value}}
    `, 40 | 41 | get value() { 42 | this._super(...arguments); 43 | 44 | this.exampleTask.perform(); 45 | this.exampleTask.perform(); 46 | 47 | return 'value'; 48 | }, 49 | 50 | exampleTask: task(function* () { 51 | yield timeout(100); 52 | }).restartable(), 53 | }), 54 | ); 55 | 56 | await render(hbs``); 57 | assert.ok(true, 'Renders'); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /packages/test-app/tests/integration/template-imports-test.gts: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import BasicTemplateImports from 'test-app/components/tests/basic-template-imports'; 4 | import { click, render } from '@ember/test-helpers'; 5 | 6 | module('Integration | template imports / SFC test', function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it works', async function (assert) { 10 | await render(); 11 | 12 | assert.dom().containsText('idle'); 13 | 14 | await click('button#perform-curried'); 15 | assert.dom().containsText('foo'); 16 | 17 | await click('button#perform-promise'); 18 | assert.dom().containsText('running'); 19 | 20 | await click('button#cancel-all'); 21 | 22 | assert.dom().containsText('idle'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/test-app/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'test-app/app'; 2 | import config from 'test-app/config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | import QUnit from 'qunit'; 6 | import { setup as setupQUnitDom } from 'qunit-dom'; 7 | 8 | setupQUnitDom(QUnit.assert); 9 | 10 | QUnit.config.testTimeout = 5000; 11 | 12 | setApplication(Application.create(config.APP)); 13 | 14 | start(); 15 | -------------------------------------------------------------------------------- /packages/test-app/tests/unit/create-mock-task-in-test.js: -------------------------------------------------------------------------------- 1 | import EmberObject from '@ember/object'; 2 | import { run } from '@ember/runloop'; 3 | import { module, test } from 'qunit'; 4 | import { task } from 'ember-concurrency'; 5 | 6 | module('Unit: test environment', function () { 7 | test(`mock task can be created in a test`, function (assert) { 8 | assert.expect(1); 9 | 10 | let taskRan = false; 11 | let myMock = EmberObject.extend({ 12 | doAsync: task(function* () { 13 | taskRan = true; 14 | yield true; 15 | }), 16 | }).create(); 17 | 18 | run(() => { 19 | return myMock.doAsync.perform().then(() => { 20 | assert.ok(taskRan); 21 | }); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/test-app/tests/unit/external/scheduler-policies/drop-policy-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import DropPolicy from 'ember-concurrency/-private/external/scheduler/policies/drop-policy'; 3 | import { typesFor } from './helpers'; 4 | 5 | module('Unit: Drop policy', function () { 6 | test('maxConcurrency 1 cancels the earliest running instance', function (assert) { 7 | let policy = new DropPolicy(1); 8 | assert.deepEqual(typesFor(policy, 0, 1), ['STARTED']); 9 | assert.deepEqual(typesFor(policy, 1, 1), ['STARTED', 'CANCELLED']); 10 | assert.deepEqual(typesFor(policy, 1, 2), [ 11 | 'STARTED', 12 | 'CANCELLED', 13 | 'CANCELLED', 14 | ]); 15 | assert.deepEqual(typesFor(policy, 1, 3), [ 16 | 'STARTED', 17 | 'CANCELLED', 18 | 'CANCELLED', 19 | 'CANCELLED', 20 | ]); 21 | }); 22 | 23 | test('maxConcurrency 2 keeps the first two running', function (assert) { 24 | let policy = new DropPolicy(2); 25 | assert.deepEqual(typesFor(policy, 0, 1), ['STARTED']); 26 | assert.deepEqual(typesFor(policy, 1, 1), ['STARTED', 'STARTED']); 27 | assert.deepEqual(typesFor(policy, 1, 2), [ 28 | 'STARTED', 29 | 'STARTED', 30 | 'CANCELLED', 31 | ]); 32 | assert.deepEqual(typesFor(policy, 1, 3), [ 33 | 'STARTED', 34 | 'STARTED', 35 | 'CANCELLED', 36 | 'CANCELLED', 37 | ]); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/test-app/tests/unit/external/scheduler-policies/enqueued-policy-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import EnqueuedPolicy from 'ember-concurrency/-private/external/scheduler/policies/enqueued-policy'; 3 | import { typesFor } from './helpers'; 4 | 5 | module('Unit: Enqueued policy', function () { 6 | test('maxConcurrency 1 cancels the earliest running instance', function (assert) { 7 | let policy = new EnqueuedPolicy(1); 8 | assert.deepEqual(typesFor(policy, 0, 1), ['STARTED']); 9 | assert.deepEqual(typesFor(policy, 1, 1), ['STARTED', 'QUEUED']); 10 | assert.deepEqual(typesFor(policy, 1, 2), ['STARTED', 'QUEUED', 'QUEUED']); 11 | assert.deepEqual(typesFor(policy, 1, 3), [ 12 | 'STARTED', 13 | 'QUEUED', 14 | 'QUEUED', 15 | 'QUEUED', 16 | ]); 17 | }); 18 | 19 | test('maxConcurrency 2 keeps the first two running', function (assert) { 20 | let policy = new EnqueuedPolicy(2); 21 | assert.deepEqual(typesFor(policy, 0, 1), ['STARTED']); 22 | assert.deepEqual(typesFor(policy, 1, 1), ['STARTED', 'STARTED']); 23 | assert.deepEqual(typesFor(policy, 1, 2), ['STARTED', 'STARTED', 'QUEUED']); 24 | assert.deepEqual(typesFor(policy, 1, 3), [ 25 | 'STARTED', 26 | 'STARTED', 27 | 'QUEUED', 28 | 'QUEUED', 29 | ]); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/test-app/tests/unit/external/scheduler-policies/helpers.js: -------------------------------------------------------------------------------- 1 | export function testScheduler(policy, numRunning, numQueued) { 2 | let reducer = policy.makeReducer(numRunning, numQueued); 3 | let total = numRunning + numQueued; 4 | return [...Array(total)].map(() => reducer.step()); 5 | } 6 | 7 | export function typesFor(policy, numRunning, numQueued) { 8 | return testScheduler(policy, numRunning, numQueued).map((a) => a.type); 9 | } 10 | -------------------------------------------------------------------------------- /packages/test-app/tests/unit/external/scheduler-policies/keep-latest-policy-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import KeepLatestPolicy from 'ember-concurrency/-private/external/scheduler/policies/keep-latest-policy'; 3 | import { typesFor } from './helpers'; 4 | 5 | module('Unit: KeepLatest policy', function () { 6 | test('maxConcurrency 1 keeps the first one running, cancels all in between', function (assert) { 7 | let policy = new KeepLatestPolicy(1); 8 | assert.deepEqual(typesFor(policy, 0, 1), ['STARTED']); 9 | assert.deepEqual(typesFor(policy, 1, 1), ['STARTED', 'QUEUED']); 10 | assert.deepEqual(typesFor(policy, 1, 2), [ 11 | 'STARTED', 12 | 'CANCELLED', 13 | 'QUEUED', 14 | ]); 15 | assert.deepEqual(typesFor(policy, 1, 3), [ 16 | 'STARTED', 17 | 'CANCELLED', 18 | 'CANCELLED', 19 | 'QUEUED', 20 | ]); 21 | }); 22 | 23 | test('maxConcurrency 2 keeps the first two running, cancels all in between', function (assert) { 24 | let policy = new KeepLatestPolicy(2); 25 | assert.deepEqual(typesFor(policy, 0, 1), ['STARTED']); 26 | assert.deepEqual(typesFor(policy, 2, 1), ['STARTED', 'STARTED', 'QUEUED']); 27 | assert.deepEqual(typesFor(policy, 2, 1), ['STARTED', 'STARTED', 'QUEUED']); 28 | assert.deepEqual(typesFor(policy, 2, 2), [ 29 | 'STARTED', 30 | 'STARTED', 31 | 'CANCELLED', 32 | 'QUEUED', 33 | ]); 34 | assert.deepEqual(typesFor(policy, 2, 3), [ 35 | 'STARTED', 36 | 'STARTED', 37 | 'CANCELLED', 38 | 'CANCELLED', 39 | 'QUEUED', 40 | ]); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/test-app/tests/unit/external/scheduler-policies/restartable-policy-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import RestartablePolicy from 'ember-concurrency/-private/external/scheduler/policies/restartable-policy'; 3 | import { typesFor } from './helpers'; 4 | 5 | module('Unit: Restartable policy', function () { 6 | test('maxConcurrency 1 cancels the earliest running instance', function (assert) { 7 | let policy = new RestartablePolicy(1); 8 | assert.deepEqual(typesFor(policy, 0, 1), ['STARTED']); 9 | assert.deepEqual(typesFor(policy, 1, 1), ['CANCELLED', 'STARTED']); 10 | assert.deepEqual(typesFor(policy, 1, 2), [ 11 | 'CANCELLED', 12 | 'CANCELLED', 13 | 'STARTED', 14 | ]); 15 | assert.deepEqual(typesFor(policy, 1, 3), [ 16 | 'CANCELLED', 17 | 'CANCELLED', 18 | 'CANCELLED', 19 | 'STARTED', 20 | ]); 21 | }); 22 | 23 | test('maxConcurrency 2 keeps the first two running', function (assert) { 24 | let policy = new RestartablePolicy(2); 25 | assert.deepEqual(typesFor(policy, 0, 1), ['STARTED']); 26 | assert.deepEqual(typesFor(policy, 1, 1), ['STARTED', 'STARTED']); 27 | assert.deepEqual(typesFor(policy, 1, 2), [ 28 | 'CANCELLED', 29 | 'STARTED', 30 | 'STARTED', 31 | ]); 32 | assert.deepEqual(typesFor(policy, 1, 3), [ 33 | 'CANCELLED', 34 | 'CANCELLED', 35 | 'STARTED', 36 | 'STARTED', 37 | ]); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/test-app/tests/unit/external/scheduler-policies/unbounded-policy-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import UnboundedPolicy from 'ember-concurrency/-private/external/scheduler/policies/unbounded-policy'; 3 | import { typesFor } from './helpers'; 4 | 5 | module('Unit: Unbounded policy', function () { 6 | test('always requests that the instance be started', function (assert) { 7 | let policy = new UnboundedPolicy(); 8 | assert.deepEqual(typesFor(policy, 0, 1), ['STARTED']); 9 | assert.deepEqual(typesFor(policy, 1, 1), ['STARTED', 'STARTED']); 10 | assert.deepEqual(typesFor(policy, 1, 2), ['STARTED', 'STARTED', 'STARTED']); 11 | assert.deepEqual(typesFor(policy, 1, 3), [ 12 | 'STARTED', 13 | 'STARTED', 14 | 'STARTED', 15 | 'STARTED', 16 | ]); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/test-app/tests/unit/generator-method-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { run } from '@ember/runloop'; 3 | import { 4 | task, 5 | restartableTask, 6 | dropTask, 7 | keepLatestTask, 8 | enqueueTask, 9 | } from 'ember-concurrency'; 10 | 11 | module('Unit | generator method', function () { 12 | test('Basic decorators functionality', function (assert) { 13 | assert.expect(5); 14 | 15 | class TestSubject { 16 | @task 17 | *doStuff() { 18 | yield; 19 | return 123; 20 | } 21 | 22 | @restartableTask 23 | *a() { 24 | yield; 25 | return 456; 26 | } 27 | 28 | @keepLatestTask 29 | *b() { 30 | yield; 31 | return 789; 32 | } 33 | 34 | @dropTask 35 | *c() { 36 | yield; 37 | return 12; 38 | } 39 | 40 | @enqueueTask 41 | *d() { 42 | yield; 43 | return 34; 44 | } 45 | } 46 | 47 | let subject; 48 | run(() => { 49 | subject = new TestSubject(); 50 | subject.doStuff.perform(); 51 | subject.a.perform(); 52 | subject.b.perform(); 53 | subject.c.perform(); 54 | subject.d.perform(); 55 | }); 56 | assert.strictEqual(subject.doStuff.last.value, 123); 57 | assert.strictEqual(subject.a.last.value, 456); 58 | assert.strictEqual(subject.b.last.value, 789); 59 | assert.strictEqual(subject.c.last.value, 12); 60 | assert.strictEqual(subject.d.last.value, 34); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /packages/test-app/tests/unit/last-value-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import EmberObject from '@ember/object'; 3 | import { task, lastValue } from 'ember-concurrency'; 4 | 5 | module('Unit | lastValue', function () { 6 | test('without a default value', async function (assert) { 7 | class ObjectWithTask extends EmberObject { 8 | @task task = function* () { 9 | return yield 'foo'; 10 | }; 11 | 12 | @lastValue('task') value; 13 | } 14 | 15 | const instance = ObjectWithTask.create(); 16 | assert.strictEqual( 17 | instance.value, 18 | undefined, 19 | 'it returns nothing if the task has not been performed', 20 | ); 21 | 22 | await instance.task.perform(); 23 | 24 | assert.strictEqual( 25 | instance.value, 26 | 'foo', 27 | 'returning the last successful value', 28 | ); 29 | }); 30 | 31 | test('with a default value', async function (assert) { 32 | class ObjectWithTaskDefaultValue extends EmberObject { 33 | @task task = function* () { 34 | return yield 'foo'; 35 | }; 36 | 37 | @lastValue('task') value = 'default value'; 38 | } 39 | 40 | const instance = ObjectWithTaskDefaultValue.create(); 41 | 42 | assert.strictEqual( 43 | instance.value, 44 | 'default value', 45 | 'it returns the default value if the task has not been performed', 46 | ); 47 | 48 | await instance.task.perform(); 49 | 50 | assert.strictEqual( 51 | instance.value, 52 | 'foo', 53 | 'returning the last successful value', 54 | ); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember/tsconfig.json", 3 | "glint": { 4 | "environment": ["ember-loose", "ember-template-imports"] 5 | }, 6 | "compilerOptions": { 7 | // The combination of `baseUrl` with `paths` allows Ember's classic package 8 | // layout, which is not resolvable with the Node resolution algorithm, to 9 | // work with TypeScript. 10 | "baseUrl": ".", 11 | "paths": { 12 | "test-app/tests/*": ["tests/*"], 13 | "test-app/*": ["app/*"], 14 | "*": ["types/*"] 15 | } 16 | }, 17 | "exclude": ["snippets"] 18 | } 19 | -------------------------------------------------------------------------------- /packages/test-app/types/global.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machty/ember-concurrency/01625f5e78c87c65382f5836068ca88ceefad3f3/packages/test-app/types/global.d.ts -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | --------------------------------------------------------------------------------