├── .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 |
2 |
3 |
4 |
5 | ember-concurrency
6 |
7 |
8 | (v {{this.addonVersion}} )
9 |
10 |
11 |
12 |
17 |
22 |
23 |
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 |
28 |
33 |
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 | task.perform()
3 | Clear Timeline
4 | {{#if @task.isRunning}}
5 | Cancel All
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 |
10 | {{yield}}
11 |
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 | Perform Task
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 | Pick
3 | Random Number
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 | Perform Task
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 | Perform Task
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 |
35 |
36 | {{#let (curryTask this.valueTask 'foo') as |curriedTask|}}
37 |
42 | Perform Curried Value Task
43 |
44 |
{{this.value}}
45 | {{/let}}
46 |
47 |
52 | Perform Promise Task
53 |
54 |
55 |
60 | Cancel
61 |
62 |
{{if this.promiseTask.isRunning 'running' 'idle'}}
63 |
64 |
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 |
5 | Find Nearby Stores
6 |
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 |
5 | Find Nearby Stores
6 | {{#if this.isFindingStores}}
7 | {{! ++ }}
8 |
9 | {{/if}}
10 |
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 |
5 | Find Nearby Stores
6 | {{#if this.isFindingStores}}
7 |
8 | {{/if}}
9 |
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 |
5 | Find Nearby Stores
6 | {{#if this.isFindingStores}}
7 |
8 | {{/if}}
9 |
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 |
5 | Find Nearby Stores
6 | {{#if this.isFindingStores}}
7 |
8 | {{/if}}
9 |
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 |
5 | Find Nearby Stores
6 | {{#if this.isFindingStores}}
7 |
8 | {{/if}}
9 |
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 |
6 | {{! ++ }}
7 | Find Nearby Stores
8 |
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 |
5 | Find Nearby Stores
6 | {{#if this.findStores.isRunning}}
7 | {{! ++ }}
8 |
9 | {{/if}}
10 |
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 |
5 | Find Nearby Stores
6 | {{#if this.findStores.isRunning}}
7 |
8 | {{/if}}
9 |
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 |
5 | Find Nearby Stores
6 | {{#if this.findStores.isRunning}}
7 |
8 | {{/if}}
9 |
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 | await
ing 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 |
23 | {{#if this.parentTask.isRunning}}
24 | Restart Parent Task
25 | {{else}}
26 | Perform Parent Task
27 | {{/if}}
28 |
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 |
41 | Search GitHub...
42 |
43 |
48 |
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 |
40 | {{#if this.askQuestion.isIdle}}
41 | Ask
42 | {{else}}
43 | Thinking...
44 |
45 | {{/if}}
46 |
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 |
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 | Cancel
7 | Return a Value
8 |
9 | Maybe Null Task
10 | Setup Task
11 | Set value option
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 | *
14 | * Click Me
15 | *
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 | clickme
6 |
7 | another one
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 |
3 | {{yield}}
4 |
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 |
17 |
18 | Task with no Params (.perform method) (RECOMMENDED)
19 |
20 |
21 |
22 | Task with no Params (with classic perform helper)
23 |
24 |
25 |
26 | Task with Params (currying with fn helper) (RECOMMENDED)
27 |
28 |
29 |
30 | Task with Params (currying with classic perform helper)
31 |
32 |
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 |
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 |
--------------------------------------------------------------------------------