├── .gitignore ├── .editorconfig ├── spec.html ├── CODE_OF_CONDUCT.md ├── package.json ├── LICENSE.md ├── HISTORY.md ├── CONTRIBUTING.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | out 4 | dist 5 | npm-debug.log 6 | deploy_key 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /spec.html: -------------------------------------------------------------------------------- 1 |
 2 | title: ES Function.prototype.once (2021)
 3 | status: proposal
 4 | stage: 0
 5 | location: https://github.com/js-choi/proposal-function-helpers
 6 | copyright: false
 7 | contributors: J. S. Choi
 8 | 
9 | 10 | 11 | 12 | 13 |

Introduction

14 |
15 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | This repository is a TC39 project, and it therefore subscribes to its [code of 3 | conduct][CoC]. It is available at . 4 | 5 | We all should strive here to be respectful, friendly and patient, inclusive, 6 | considerate, and careful in the words we choose. When we disagree, we should try 7 | to understand why. 8 | 9 | To ask a question or report an issue, please follow the [CoC]’s directions, 10 | e.g., emailing [tc39-conduct-reports@googlegroups.com][]. 11 | 12 | More information about contributing is also available in [CONTRIBUTING.md][]. 13 | 14 | [CoC]: https://tc39.es/code-of-conduct/ 15 | [tc39-conduct-reports@googlegroups.com]: mailto:tc39-conduct-reports@googlegroups.com 16 | [CONTRIBUTING.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/CONTRIBUTING.md 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proposal-function-once", 3 | "private": true, 4 | "description": "A TC39 proposal for an Function.prototype.once method in the JavaScript language.", 5 | "author": "J. S. Choi (https://jschoi.org/)", 6 | "license": "BSD-3-Clause", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/tc39/proposal-array-async-from.git" 10 | }, 11 | "keywords": [ 12 | "proposal", 13 | "tc39", 14 | "function", 15 | "callback" 16 | ], 17 | "scripts": { 18 | "prebuild": "mkdir -p dist", 19 | "build": "ecmarkup --verbose spec.html dist/index.html --css dist/ecmarkup.css --js dist/ecmarkup.js", 20 | "watch": "npm run build -- --watch" 21 | }, 22 | "devDependencies": { 23 | "ecmarkup": "^10.0.2" 24 | }, 25 | "homepage": "https://github.com/js-choi/proposal-function-once" 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2022 J. S. Choi 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | **This software is provided by the copyright holders and contributors 19 | “as is” and any express or implied warranties, including, but not 20 | limited to, the implied warranties of merchantability and fitness for a 21 | particular purpose are disclaimed. In no event shall the copyright 22 | holder or contributors be liable for any direct, indirect, incidental, 23 | special, exemplary, or consequential damages (including, but not limited 24 | to, procurement of substitute goods or services; loss of use, data, or 25 | profits; or business interruption) however caused and on any theory of 26 | liability, whether in contract, strict liability, or tort (including 27 | negligence or otherwise) arising in any way out of the use of this 28 | software, even if advised of the possibility of such damage.** 29 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # Brief history of ES Function.prototype.once 2 | For information on what “Stage 1” and “Stage 2” mean, 3 | read about the [TC39 Process][]. 4 | 5 | More information about contributing is also available in [CONTRIBUTING.md][]. 6 | 7 | ## 2015–2021 8 | The pipe champion group presents F# pipes (a tacit-unary-function-application 9 | operator) for Stage 2 twice to TC39, being unsuccessful both times due to 10 | pushback from multiple other TC39 representatives’ memory performance concerns, 11 | syntax concerns about await, and concerns about encouraging ecosystem 12 | bifurcation/forking. 13 | 14 | For more information, see the [pipe proposal’s HISTORY.md][pipe history]. 15 | 16 | ## 2021-09 17 | Inspired by [pipe issue #233][], [@js-choi][] creates a new proposal 18 | that would add several Function helper methods. 19 | 20 | ## 2021-10 21 | [On 2021-10, proposal-function-helpers is presented to the Committee 22 | plenary][2021-10] for Stage 1. The Committee rejects the proposal due to its 23 | being overly broad and requests that it be split up into multiple proposals. 24 | 25 | ## 2022-03 26 | [On 2022-03, a TC39 incubator meeting is held][2022-03 incubator] to discuss 27 | Function helpers in general. Several representatives express support for 28 | Function.prototype.once, and it is decided to pursue Function.prototype.once 29 | for Stage 1. 30 | 31 | [TC39 process]: https://tc39.es/process-document/ 32 | [CONTRIBUTING.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/CONTRIBUTING.md 33 | 34 | [pipe history]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md 35 | [pipe issue #233]: https://github.com/tc39/proposal-pipeline-operator/issues/233 36 | 37 | [@js-choi]: https://github.com/js-choi 38 | 39 | [2021-10]: https://github.com/tc39-transfer/proposal-function-helpers/issues/17#issuecomment-953814353 40 | 41 | [2022-03 incubator]: https://github.com/tc39/incubator-agendas/blob/main/notes/2022/03-08.md 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this proposal 2 | First off, thank you for taking the time to contribute! 🎉 3 | 4 | Here are some suggestions to contributing to this proposal. 5 | 6 | 1. The general [TC39 Process][], which summarizes 7 | how TC39’s “consensus” and “Stages” work. 8 | 2. The guide on [contributing to TC39 proposals][contributing guide]. 9 | 3. The [TC39 Code of Conduct][CoC]: 10 | It has important information about how we’re all expected to act 11 | and what to do when we feel like someone’s conduct does not meet the Code. 12 | We all want to maintain a friendly, productive working environment! 13 | 4. The [TC39 How to Give Feedback][feedback] article. 14 | 5. The [proposal explainer][] to make sure that it is 15 | not already addressed there. 16 | 6. The [TC39 Matrix guide][] (if you want to chat with TC39 members on Matrix, 17 | which is a real-time chat platform). 18 | 7. If the explainer does not already explain your topic adequately, 19 | then please [search the GitHub repository’s issues][issues] 20 | to see if any issues match the topic you had in mind. 21 | This proposal is more than four years old, 22 | and it is likely that the topic has already been raised and thoroughly discussed. 23 | 24 | You can leave a comment on an [existing GitHub issue][issues], 25 | create a new issue (but do try to [find an existing GitHub issue][issues] first), 26 | or [participate on Matrix][TC39 Matrix guide]. 27 | 28 | Please try to keep any existing GitHub issues on their original topic. 29 | 30 | If you feel that someone’s conduct is not meeting the [TC39 Code of Conduct][CoC], 31 | whether in this GitHub repository or in a [TC39 Matrix room][TC39 Matrix guide], 32 | then please follow the [Code of Conduct][CoC]’s directions for reporting the violation, 33 | including emailing [tc39-conduct-reports@googlegroups.com][]. 34 | 35 | Thank you again for taking the time to contribute! 36 | 37 | [CoC]: https://tc39.es/code-of-conduct/ 38 | [TC39 process]: https://tc39.es/process-document/ 39 | [contributing guide]: https://github.com/tc39/ecma262/blob/master/CONTRIBUTING.md#new-feature-proposals 40 | [feedback]: https://github.com/tc39/how-we-work/blob/master/feedback.md 41 | [proposal explainer]: https://github.com/tc39/proposal-array-from-async/blob/main/README.md 42 | [TC39 Matrix guide]: https://github.com/tc39/how-we-work/blob/master/matrix-guide.md 43 | [issues]: https://github.com/tc39/proposal-array-from-async/issues?q=is%3Aissue+ 44 | [tc39-conduct-reports@googlegroups.com]: mailto:tc39-conduct-reports@googlegroups.com 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Function.prototype.once for JavaScript 2 | ECMAScript Stage-1 Proposal. 2022. 3 | 4 | Co-champions: Hemanth HM; J. S. Choi. 5 | 6 | ## Rationale 7 | It is often useful to ensure that callbacks execute only once, no matter how 8 | many times are those callbacks called. To do this, developers frequently use 9 | “once” functions, which wrap around those callbacks and ensure they are called 10 | at most once. This proposal would standardize such a once function in the 11 | language core. 12 | 13 | ## Description 14 | The Function.prototype.once method would create a new function that calls the 15 | original function at most once, no matter how much the new function is called. 16 | Arguments given in this call are passed to the original function. Any 17 | subsequent calls to the created function would return the result of its first 18 | call. 19 | 20 | ```js 21 | function f (x) { console.log(x); return x * 2; } 22 | 23 | const fOnce = f.once(); 24 | fOnce(3); // Prints 3 and returns 6. 25 | fOnce(3); // Does not print anything. Returns 6. 26 | fOnce(2); // Does not print anything. Returns 6. 27 | ``` 28 | 29 | ## Real-world examples 30 | The following code was adapted to use this proposal. 31 | 32 | From [execa@6.1.0][]: 33 | ```js 34 | export function execa (file, args, options) { 35 | /* … */ 36 | const handlePromise = async () => { /* … */ }; 37 | const handlePromiseOnce = handlePromise.once(); 38 | /* … */ 39 | return mergePromise(spawned, handlePromiseOnce); 40 | }); 41 | ``` 42 | 43 | From [glob@7.2.1][]: 44 | ```js 45 | function Glob (pattern, options, cb) { 46 | /* … */ 47 | if (typeof cb === 'function') { 48 | cb = cb.once(); 49 | this.on('error', cb); 50 | this.on('end', function (matches) { 51 | cb(null, matches); 52 | }) 53 | } /* … */ 54 | }); 55 | ``` 56 | 57 | From [Meteor@2.6.1][]: 58 | ```js 59 | // “Are we running Meteor from a git checkout?” 60 | export const inCheckout = (function () { 61 | try { /* … */ } catch (e) { console.log(e); } 62 | return false; 63 | }).once(); 64 | ``` 65 | 66 | From [cypress@9.5.2][]: 67 | ```js 68 | cy.on('command:retry', (() => { /* … */ }).once()); 69 | ``` 70 | 71 | From [jitsi-meet 1.0.5913][]: 72 | ```js 73 | this._hangup = (() => { 74 | sendAnalytics(createToolbarEvent('hangup')); 75 | /* … */ 76 | }).once(); 77 | ``` 78 | 79 | [execa@6.1.0]: https://github.com/sindresorhus/execa/blob/v6.1.0/index.js 80 | [glob@7.2.1]: https://github.com/isaacs/node-glob/blob/v7.2.1/glob.js 81 | [Meteor@2.6.1]: https://github.com/meteor/meteor/blob/release/METEOR%402.6.1/tools/fs/files.ts 82 | [cypress@9.5.2]: https://github.com/cypress-io/cypress/blob/v9.5.2/packages/driver/cypress/integration/commands/waiting_spec.js 83 | [jitsi-meet 1.0.5913]: https://github.com/jitsi/jitsi-meet/blob/stable/jitsi-meet_7001/react/features/toolbox/components/HangupButton.js 84 | 85 | ## Precedents and web compatibility 86 | 87 | There is a [popular NPM library called once][NPM once] that allows monkey 88 | patching, which may raise concerns about Function.prototype.once’s web 89 | compatibility. 90 | 91 | However, since its first public version, the once library’s monkey patching has 92 | been opt-in only. The monkey patching is not conditional, and there is no actual web-compatibility risk from this library. 93 | 94 | ```js 95 | // The default form exports a function. 96 | once = require('once'); 97 | fOnce = once(f); 98 | 99 | // The opt-in form monkey patches Function.prototype. 100 | require('once').proto(); 101 | fOnce = f.once(); 102 | ``` 103 | 104 | Other popular once functions from libraries (e.g., [lodash.once][], [Underscore][] and [onetime][]) also do not use conditional monkey patching. 105 | 106 | A [code search for `!Function.prototype.once`][code search] (as in `if 107 | (!Function.prototype.once) { /* monkey patching */ }`) also gave no results in 108 | any indexed open-source code. It is unlikely that any production code on the 109 | web is conditionally monkey patching a once method into Function.prototype. 110 | 111 | [NPM once]: https://www.npmjs.com/package/once 112 | [lodash.once]: https://www.npmjs.com/package/lodash.once 113 | [Underscore]: https://www.npmjs.com/package/underscore 114 | [onetime]: https://www.npmjs.com/package/onetime 115 | [code search]: https://sourcegraph.com/search?q=context:global+%21Function.prototype.once&patternType=literal 116 | --------------------------------------------------------------------------------