├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .github └── workflows │ ├── dev.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── .gitkeep └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | end_of_line = lf 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "gulp" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: dev 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | env: 9 | CI: true 10 | 11 | jobs: 12 | prettier: 13 | name: Format code 14 | runs-on: ubuntu-latest 15 | if: ${{ github.event_name == 'push' }} 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Prettier 22 | uses: gulpjs/prettier_action@v3.0 23 | with: 24 | commit_message: 'chore: Run prettier' 25 | prettier_options: '--write .' 26 | 27 | test: 28 | name: Tests for Node ${{ matrix.node }} on ${{ matrix.os }} 29 | runs-on: ${{ matrix.os }} 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | node: [10, 12, 14, 16] 35 | os: [ubuntu-latest, windows-latest, macos-latest] 36 | 37 | steps: 38 | - name: Clone repository 39 | uses: actions/checkout@v2 40 | 41 | - name: Set Node.js version 42 | uses: actions/setup-node@v2 43 | with: 44 | node-version: ${{ matrix.node }} 45 | 46 | - run: node --version 47 | - run: npm --version 48 | 49 | - name: Install npm dependencies 50 | run: npm install 51 | 52 | - name: Run lint 53 | run: npm run lint 54 | 55 | - name: Run tests 56 | run: npm test 57 | 58 | - name: Coveralls 59 | uses: coverallsapp/github-action@v1.1.2 60 | with: 61 | github-token: ${{ secrets.GITHUB_TOKEN }} 62 | flag-name: ${{matrix.os}}-node-${{ matrix.node }} 63 | parallel: true 64 | 65 | coveralls: 66 | needs: test 67 | name: Finish up 68 | 69 | runs-on: ubuntu-latest 70 | steps: 71 | - name: Coveralls Finished 72 | uses: coverallsapp/github-action@v1.1.2 73 | with: 74 | github-token: ${{ secrets.GITHUB_TOKEN }} 75 | parallel-finished: true 76 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | 8 | jobs: 9 | release-please: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: GoogleCloudPlatform/release-please-action@v2 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | release-type: node 16 | package-name: release-please-action 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Garbage files 64 | .DS_Store 65 | 66 | # Generated by integration tests 67 | test/fixtures/tmp 68 | test/fixtures/out 69 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | .nyc_output/ 3 | CHANGELOG.md 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.0.0](https://www.github.com/gulpjs/async-once/compare/v1.0.1...v2.0.0) (2022-06-28) 4 | 5 | 6 | ### ⚠ BREAKING CHANGES 7 | 8 | * Normalize repository, dropping node <10.13 support (#10) 9 | 10 | ### Miscellaneous Chores 11 | 12 | * Normalize repository, dropping node <10.13 support ([#10](https://www.github.com/gulpjs/async-once/issues/10)) ([7bcae07](https://www.github.com/gulpjs/async-once/commit/7bcae075f5aa4eb9430a39865f41c531149d6c2a)) 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015, 2017, 2022 Blaine Bublitz and Eric Schoffstall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | # async-once 8 | 9 | [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][ci-image]][ci-url] [![Coveralls Status][coveralls-image]][coveralls-url] 10 | 11 | Guarantee a node-style async function is only executed once. 12 | 13 | ## Usage 14 | 15 | ```js 16 | var once = require('async-once'); 17 | 18 | var count = 0; 19 | 20 | var myAsyncFunc = once(function (cb) { 21 | count++; 22 | cb(null, count); 23 | }); 24 | 25 | myAsyncFunc(function (err, result) { 26 | assert(result === 1); 27 | }); 28 | 29 | myAsyncFunc(function (err, result) { 30 | assert(result === 1); 31 | }); 32 | 33 | assert(count === 1); 34 | ``` 35 | 36 | ## API 37 | 38 | ### `once(fn)` 39 | 40 | Takes a node-style async function (`fn`) to ensure it's only called once. The function should accept a callback as its last parameter which is called with `cb(err, result)`. Returns a function that can be called any number of times but will only execute once. Arguments passed to the returned function will be passed to the `fn`. 41 | 42 | ## License 43 | 44 | MIT 45 | 46 | 47 | [downloads-image]: https://img.shields.io/npm/dm/async-once.svg?style=flat-square 48 | [npm-url]: https://www.npmjs.com/package/async-once 49 | [npm-image]: https://img.shields.io/npm/v/async-once.svg?style=flat-square 50 | 51 | [ci-url]: https://github.com/gulpjs/async-once/actions?query=workflow:dev 52 | [ci-image]: https://img.shields.io/github/actions/workflow/status/gulpjs/async-once/dev.yml?branch=master&style=flat-square 53 | 54 | [coveralls-url]: https://coveralls.io/r/gulpjs/async-once 55 | [coveralls-image]: https://img.shields.io/coveralls/gulpjs/async-once/master.svg?style=flat-square 56 | 57 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Queue = require('@mapbox/basic-queue'); 4 | var wrappy = require('wrappy'); 5 | 6 | var slice = Array.prototype.slice; 7 | 8 | function asyncOnce(fn) { 9 | // Our state 10 | var _err; 11 | var _result; 12 | var _called = false; 13 | 14 | function worker(work, done) { 15 | var ctx = work.ctx; 16 | var initial = work.initial; 17 | var cb = work.cb; 18 | 19 | function updateState(err, result) { 20 | _err = err; 21 | _result = result; 22 | _called = true; 23 | cb(_err, _result); 24 | done(); 25 | } 26 | 27 | if (_called) { 28 | cb(_err, _result); 29 | done(); 30 | } else { 31 | fn.apply(ctx, initial.concat(updateState)); 32 | } 33 | } 34 | 35 | var _queue = new Queue(worker, 1); 36 | 37 | return function () { 38 | var args = slice.call(arguments, 0); 39 | var work = { 40 | ctx: this, 41 | initial: args.slice(0, -1), 42 | cb: args.slice(-1)[0], 43 | }; 44 | 45 | if (typeof work.cb !== 'function') { 46 | throw new Error('async-once only works with node-style async functions'); 47 | } 48 | 49 | _queue.add(work); 50 | }; 51 | } 52 | 53 | module.exports = wrappy(asyncOnce); 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-once", 3 | "version": "2.0.0", 4 | "description": "Guarantee a node-style async function is only executed once.", 5 | "author": "Gulp Team (https://gulpjs.com/)", 6 | "contributors": [ 7 | "Blaine Bublitz " 8 | ], 9 | "repository": "gulpjs/async-once", 10 | "license": "MIT", 11 | "engines": { 12 | "node": ">=10.13.0" 13 | }, 14 | "main": "index.js", 15 | "files": [ 16 | "LICENSE", 17 | "index.js" 18 | ], 19 | "scripts": { 20 | "lint": "eslint .", 21 | "pretest": "npm run lint", 22 | "test": "nyc mocha --async-only" 23 | }, 24 | "dependencies": { 25 | "@mapbox/basic-queue": "^1.0.1", 26 | "wrappy": "^1.0.2" 27 | }, 28 | "devDependencies": { 29 | "eslint": "^7.0.0", 30 | "eslint-config-gulp": "^5.0.0", 31 | "eslint-plugin-node": "^11.1.0", 32 | "expect": "^27.0.0", 33 | "mocha": "^8.0.0", 34 | "nyc": "^15.0.0" 35 | }, 36 | "nyc": { 37 | "reporter": [ 38 | "lcov", 39 | "text-summary" 40 | ] 41 | }, 42 | "prettier": { 43 | "singleQuote": true 44 | }, 45 | "keywords": [ 46 | "once", 47 | "async", 48 | "callback", 49 | "one", 50 | "time" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/async-once/462f9a558fe232f2c6c2dcadc744fd1a43059fa0/test/.gitkeep -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var once = require('../'); 6 | 7 | describe('async-once', function () { 8 | it('calls once', function (done) { 9 | var count = 0; 10 | 11 | var fn = once(function (cb) { 12 | count++; 13 | cb(null, count); 14 | }); 15 | 16 | fn(function (err, result) { 17 | expect(count).toEqual(1); 18 | expect(result).toEqual(1); 19 | }); 20 | 21 | fn(function (err, result) { 22 | expect(count).toEqual(1); 23 | expect(result).toEqual(1); 24 | done(err); 25 | }); 26 | }); 27 | 28 | it('will queue multiple async runs', function (done) { 29 | var count = 0; 30 | 31 | var fn = once(function (cb) { 32 | count++; 33 | process.nextTick(function () { 34 | cb(null, count); 35 | }); 36 | }); 37 | 38 | fn(function (err, result) { 39 | expect(count).toEqual(1); 40 | expect(result).toEqual(1); 41 | }); 42 | 43 | fn(function (err, result) { 44 | expect(count).toEqual(1); 45 | expect(result).toEqual(1); 46 | done(err); 47 | }); 48 | }); 49 | 50 | it('throws on non-node-style async function', function (done) { 51 | var fn = once(function () {}); 52 | 53 | expect(fn.bind(null, 1234)).toThrow(); 54 | done(); 55 | }); 56 | 57 | it('passes extra args to the wrapped fn', function (done) { 58 | var fn = once(function (fwd, cb) { 59 | expect(fwd).toEqual(1); 60 | cb(null, fwd); 61 | }); 62 | 63 | fn(1, function (err, result) { 64 | expect(result).toEqual(1); 65 | }); 66 | 67 | fn(2, function (err, result) { 68 | expect(result).toEqual(1); 69 | done(); 70 | }); 71 | }); 72 | 73 | it('passes error from wrapped fn', function (done) { 74 | var error = new Error('boom'); 75 | 76 | var fn = once(function (cb) { 77 | cb(error); 78 | }); 79 | 80 | fn(function (err) { 81 | expect(err).toEqual(error); 82 | }); 83 | 84 | fn(function (err) { 85 | expect(err).toEqual(error); 86 | done(); 87 | }); 88 | }); 89 | }); 90 | --------------------------------------------------------------------------------