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 |
--------------------------------------------------------------------------------