2 | title: ES Function helpers (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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "proposal-function-helpers",
4 | "description": "Draft specification for several Function helpers in JavaScript.",
5 | "author": "J. S. Choi (https://jschoi.org/)",
6 | "license": "BSD-3-Clause",
7 | "repository": "github:js-choi/proposal-function-helpers",
8 | "scripts": {
9 | "prespec": "mkdir -p out/",
10 | "spec": "ecmarkup spec.html out/index.html --assets inline --contributors \"J. S. Choi, Ecma International\" --copyright true --title \"ES Function helpers (2021)\" --status \"proposal\" --stage 0",
11 | "prewatch": "npm run prespec",
12 | "watch": "npm run spec -- --watch"
13 | },
14 | "devDependencies": {
15 | "ecmarkup": "^8.0.1"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of conduct
2 | This repository is a TC39 project, and it therefore subscribes to
3 | its [code of conduct][CoC]. It is available at .
4 |
5 | We all should strive here to be respectful, friendly and patient,
6 | inclusive, considerate, and careful in the words we choose.
7 | When we disagree, we should try to understand why.
8 |
9 | To ask a question or report an issue, please follow the [CoC]’s directions, e.g., emailing
10 | [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 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2021 J. S. Choi
2 |
3 | Redistribution and use in source and binary forms,
4 | with or without modification, are permitted
5 | provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of the copyright holder nor the names of its contributors
15 | may be used to endorse or promote products derived from this software
16 | without specific prior written permission.
17 |
18 | **This software is provided by the copyright holders and contributors “as is”
19 | and any express or implied warranties, including, but not limited to,
20 | the implied warranties of merchantability and fitness for a particular purpose
21 | are disclaimed.
22 | In no event shall the copyright holder or contributors be liable
23 | for any direct, indirect, incidental, special, exemplary, or consequential damages
24 | (including, but not limited to, procurement of substitute goods or services;
25 | loss of use, data, or profits; or business interruption)
26 | however caused and on any theory of liability,
27 | whether in contract, strict liability, or tort (including negligence or otherwise)
28 | arising in any way out of the use of this software,
29 | even if advised of the possibility of such damage.**
30 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | # Brief history of JavaScript function helpers
2 | Function operations in JavaScript has a long and twisty history.
3 | That history can give essential context behind this proposal.
4 |
5 | For information on what “Stage 1” and “Stage 2” mean,
6 | read about the [TC39 Process][].
7 |
8 | More information about contributing is also available in [CONTRIBUTING.md][].
9 |
10 | ## 2015–2021
11 | The pipe champion group presents F# pipes (a tacit-unary-function-application
12 | operator) for Stage 2 twice to TC39, being unsuccessful both times due to
13 | pushback from multiple other TC39 representatives’ memory performance concerns,
14 | syntax concerns about await, and concerns about encouraging ecosystem
15 | bifurcation/forking.
16 |
17 | For more information, see the [pipe proposal’s HISTORY.md][pipe history].
18 |
19 | ## 2021-09
20 | Inspired by [pipe issue #233][], [@js-choi][] creates a new proposal
21 | that would add several Function helper methods.
22 |
23 | ## 2021-10
24 | [On 2021-10, proposal-function-helpers is presented to the Committee
25 | plenary][2021-10] for Stage 1. The Committee rejects the proposal due to its
26 | being overly broad and requests that it be split up into multiple proposals.
27 |
28 | [TC39 process]: https://tc39.es/process-document/
29 | [CONTRIBUTING.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/CONTRIBUTING.md
30 |
31 | [pipe history]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md
32 | [pipe issue #233]: https://github.com/tc39/proposal-pipeline-operator/issues/233
33 |
34 | [@js-choi]: https://github.com/js-choi
35 |
36 | [2021-10]: https://github.com/tc39-transfer/proposal-function-helpers/issues/17#issuecomment-953814353
37 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to the ES pipe operator’s proposal
2 | First off, thank you for taking the time to contribute! 🎉
3 |
4 | Here are some suggestions to contributing to this proposal.
5 | The pipe operator in JavaScript has a [long and twisty history][HISTORY.md].
6 | A lot of issues have already been argued round in round in circles.
7 |
8 | Bearing that in mind, please try to read the following
9 | before making new issues or comments:
10 |
11 | 1. [HISTORY.md][]: This will give a lot of context
12 | behind what’s been happening to the proposal since its creation in 2015.
13 | 2. The general [TC39 Process][], which summarizes
14 | how TC39’s “consensus” and “Stages” work.
15 | 3. The guide on [contributing to TC39 proposals][contributing guide].
16 | 4. The [TC39 Code of Conduct][CoC]:
17 | It has important information about how we’re all expected to act
18 | and what to do when we feel like someone’s conduct does not meet the Code.
19 | We all want to maintain a friendly, productive working environment!
20 | 5. The [TC39 How to Give Feedback][feedback] article.
21 | 6. The [proposal explainer][] to make sure that it is
22 | not already addressed there.
23 | 7. The [TC39 Matrix guide][] (if you want to chat with TC39 members on Matrix,
24 | which is a real-time chat platform).
25 |
26 | 8. If the explainer does not already explain your topic adequately,
27 | then please [search the GitHub repository’s issues][issues]
28 | to see if any issues match the topic you had in mind.
29 | This proposal is more than four years old,
30 | and it is likely that the topic has already been raised and thoroughly discussed.
31 |
32 | You can leave a comment on an [existing GitHub issue][issues],
33 | create a new issue (but really do try to [find an existing GitHub issue][issues] first),
34 | or [participate on Matrix][TC39 Matrix guide].
35 |
36 | Please try to keep any existing GitHub issues on their original topic.
37 |
38 | If you feel that someone’s conduct is not meeting the [TC39 Code of Conduct][CoC],
39 | whether in this GitHub repository or in a [TC39 Matrix room][TC39 Matrix guide],
40 | then please follow the [Code of Conduct][CoC]’s directions for reporting the violation,
41 | including emailing [tc39-conduct-reports@googlegroups.com][].
42 |
43 | Thank you again for taking the time to contribute!
44 |
45 | [HISTORY.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md
46 | [CoC]: https://tc39.es/code-of-conduct/
47 | [TC39 process]: https://tc39.es/process-document/
48 | [contributing guide]: https://github.com/tc39/ecma262/blob/master/CONTRIBUTING.md#new-feature-proposals
49 | [feedback]: https://github.com/tc39/how-we-work/blob/master/feedback.md
50 | [proposal explainer]: https://github.com/tc39/proposal-pipeline-operator/blob/main/README.md
51 | [TC39 Matrix guide]: https://github.com/tc39/how-we-work/blob/master/matrix-guide.md
52 | [issues]: https://github.com/tc39/proposal-pipeline-operator/issues?q=is%3Aissue+
53 | [tc39-conduct-reports@googlegroups.com]: mailto:tc39-conduct-reports@googlegroups.com
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Function helpers for JavaScript
2 | Withdrawn ECMAScript Stage-0 Proposal. J. S. Choi, 2021.
3 |
4 | [On 2021-10, proposal-function-helpers was presented to the Committee
5 | plenary][2021-10] for Stage 1. The Committee rejected the proposal due to its
6 | being overly broad and requests that it be split up into multiple proposals.
7 | This proposal is therefore **withdrawn** and split into
8 | [proposal-function-pipe-flow][] and [proposal-function-un-this][].
9 |
10 | [2021-10]: https://github.com/tc39-transfer/proposal-function-helpers/issues/17#issuecomment-953814353
11 | [proposal-function-pipe-flow]: https://github.com/js-choi/proposal-function-pipe-flow
12 | [proposal-function-un-this]: https://github.com/js-choi/proposal-function-un-this
13 |
14 | * [Proposal history][HISTORY.md]
15 |
16 | [HISTORY.md]: https://github.com/js-choi/proposal-function-helpers/blob/main/HISTORY.md
17 |
18 |
19 |
20 | Original proposal
21 |
22 | Several useful, common helper functions are defined, downloaded, and used a lot.
23 | We should standardize at least some of them.
24 | This proposal is seeking Committee consensus for Stage 1:
25 | that standardizing at least some Function helpers is “worth investigating”.
26 | It is not seeking to standardize every imaginable helper function:
27 | just a selected few frequently used functions.
28 | Choosing which functions to standardize would be bikeshedding for Stage 2.
29 | Alternatively, the Committee could request that this proposal
30 | be split up into multiple proposals.
31 |
32 | These convenience functions are simple,
33 | and they can be reimplemented easily in userspace.
34 | So why standardize them? Because:
35 |
36 | 1. These helper functions are commonly used and universally useful.
37 | Each function is frequently downloaded from NPM,
38 | despite their being easily reimplementable.
39 | This is to be expected:
40 | after all, every JavaScript developer needs to manipulate callbacks,
41 | but they often do not wish to write the utilities themselves.
42 | 2. Standardization would improve developer ergonomics.
43 | If we find ourselves needing these functions in a REPL or script,
44 | instead of having to download an external package
45 | or pasting in a definition into our own code,
46 | we can simply destructure the `Function` object.
47 | 3. Standardization would improve code clarity.
48 | There would be one standard name for each of these functions,
49 | rather than various names from various libraries
50 | that refer to the same thing.
51 |
52 | Unlike new syntax, standardized helper functions are relatively lightweight ways
53 | to improve the experience of all developers.
54 | These helper functions are well-trodden cowpaths,
55 | each of which deserves consideration for standardization.
56 |
57 | The following functions are only possibilities.
58 | Choosing which functions to standardize would be bikeshedding for Stage 2.
59 |
60 | ## Function.flow
61 | The `Function.flow` static method creates a new function by combining several callbacks.
62 |
63 | ```js
64 | Function.flow(...fns);
65 |
66 | const { flow } = Function;
67 |
68 | const f = flow(f0, f1, f2);
69 | f(5, 7); // f2(f1(f0(5, 7))).
70 |
71 | const g = flow(g0);
72 | g(5, 7); // g0(5, 7).
73 |
74 | const h = flow();
75 | h(5, 7); // 5.
76 | ```
77 |
78 | The following real-world examples originally used [lodash.flow][].
79 |
80 | ```js
81 | // From gatsby@3.14.3/packages/gatsby-plugin-sharp/src/plugin-options.js:
82 | flow(
83 | mapUserLinkHeaders(pluginData),
84 | applySecurityHeaders(pluginOptions),
85 | applyCachingHeaders(pluginData, pluginOptions),
86 | mapUserLinkAllPageHeaders(pluginData, pluginOptions),
87 | applyLinkHeaders(pluginData, pluginOptions),
88 | applyTransfromHeaders(pluginOptions),
89 | saveHeaders(pluginData)
90 | )
91 |
92 | // From strapi@3.6.8
93 | // packages/strapi-admin/services/permission/permissions-manager/query-builers.js:
94 | const transform = flow(flattenDeep, cleanupUnwantedProperties);
95 |
96 | // From semantic-ui-react@v2.0.4/docs/static/utils/getInfoForSeeTags.js:
97 | const getInfoForSeeTags = flow(
98 | _.get('docblock.tags'),
99 | _.filter((tag) => tag.title === 'see'),
100 | _.map((tag) => {
101 | }),
102 | )
103 | ```
104 |
105 | Any function created by `Function.flow`
106 | applies its own arguments to its leftmost callback.
107 | Then that result is applied to its next callback.
108 | In other words, function composition occurs from left to right.
109 |
110 | The leftmost callback may have any arity,
111 | but any subsequent callbacks are expected to be unary.
112 |
113 | If `Function.flow` receives no arguments, then, by default,
114 | it will return `Function.identity` (which is defined later in this proposal).
115 |
116 | Precedents include:
117 | * [lodash][]: [lodash.flow][] is individually downloaded from NPM
118 | about [600,000 times weekly][lodash.flow]
119 | * [fp-ts][]: `import { flow } from 'fp-ts/function';`
120 | * [Ramda][]: `import { pipe } from 'ramda/src/pipe';`
121 | * [RxJS][]: `import { pipe } from 'rxjs';`
122 |
123 | [lodash.flow]: https://www.npmjs.com/package/lodash.flow
124 |
125 | ## Function.flowAsync
126 | The `Function.flowAsync` static method creates a new function
127 | by combining several potentially async callbacks;
128 | the created function will always return a promise.
129 |
130 | ```js
131 | Function.flowAsync(...fns);
132 |
133 | const { flowAsync } = Function;
134 |
135 | // (...args) => Promise.resolve(x).then(f0).then(f1).then(f2).
136 | flowAsync(f0, f1, f2);
137 |
138 | const f = flowAsync(f0, f1, f2);
139 | await f(5, 7); // await f2(await f1(await f0(5, 7))).
140 |
141 | const g = flowAsync(g0);
142 | await g(5, 7); // await g0(5, 7).
143 |
144 | const h = flowAsync();
145 | await h(5, 7); // await 5.
146 | ```
147 |
148 | Any function created by `Function.flowAsync`
149 | applies its own arguments to its leftmost callback.
150 | Then that result is `await`ed before being applied to its next callback.
151 | In other words, async function composition occurs from left to right.
152 |
153 | The leftmost callback may have any arity,
154 | but any subsequent callbacks are expected to be unary.
155 |
156 | If `Function.flowAsync` receives no arguments, then, by default,
157 | it will return `Promise.resolve`.
158 |
159 | The name “flow” comes from lodash.flow.
160 | (The name compose would be confusing with other languages’ RTL function composition.)
161 |
162 | ## Function.pipe
163 | The `Function.pipe` static method applies a sequence
164 | of callbacks to a given input value, returning the final callback’s result.
165 |
166 | ```js
167 | Function.pipe(input, ...fns);
168 |
169 | const { pipe } = Function;
170 |
171 | // f2(f1(f0(5))).
172 | pipe(5, f0, f1, f2);
173 |
174 | // 5.
175 | pipe(5);
176 |
177 | // undefined.
178 | pipe();
179 | ```
180 |
181 | The following real-world examples originally used [fp-ts][]’s `pipe` function.
182 |
183 | ```js
184 | // From @gripeless/pico@1.0.1/source/inline.ts:
185 | return pipe(
186 | download(absoluteURL),
187 | mapRej(downloadErrorToDetailedError),
188 | chainFluture(responseToBlob),
189 | chainFluture(blobToDataURL),
190 | mapFluture(dataURL => `url(${dataURL})`)
191 | )
192 |
193 | // From StoplightIO Prism v4.5.0 packages/http/src/validator/validators/body.ts:
194 | return pipe(
195 | specs,
196 | A.findFirst(spec => !!typeIs(mediaType, [spec.mediaType])),
197 | O.alt(() => A.head(specs)),
198 | O.map(content => ({ mediaType, content }))
199 | );
200 | ```
201 |
202 | The first callback is applied to `input`,
203 | then the second callback is applied to the first callback’s result,
204 | and so forth.
205 | In other words, function piping occurs from left to right.
206 |
207 | Each callback is expected to be a unary function.
208 |
209 | If `Function.pipe` receives only one argument, then it will return `input` by default.\
210 | If `Function.pipe` receives no arguments, then it will return `undefined`.
211 |
212 | Precedents include:
213 | * [fp-ts][]: `import { pipe } from 'fp-ts/function';`
214 |
215 | ***
216 |
217 |
218 | What happened to the F# pipe operator?
219 |
220 | ***
221 |
222 | F#, Haskell, and other languages that are based on auto-curried unary functions
223 | have a tacit-unary-function-application operator.
224 | The pipe champion group has presented F# pipes for Stage 2 twice to TC39,
225 | being unsuccessful both times
226 | due to pushback from multiple other TC39 representatives’
227 | memory performance concerns, syntax concerns about await,
228 | and concerns about encouraging ecosystem bifurcation/forking.
229 | (For more information, see the [pipe proposal’s HISTORY.md][pipe history].)
230 |
231 | Given this reality, TC39 is much more likely to pass
232 | a `Function.pipe` helper function than a similar syntactic operator.
233 |
234 | Standardizing a helper function does not preclude
235 | standardizing an equivalent operator later.
236 | For example, TC39 standardized binary `**` even when `Math.pow` existed.
237 |
238 | In the future, we might try to propose a F# pipe operator,
239 | but we would like to try proposing `Function.pipe` first,
240 | in an effort to bring its benefits to the wider JavaScript community
241 | as soon as possible.
242 |
243 |
244 |
245 | ## Function.pipeAsync
246 | The `Function.pipeAsync` static method applies a sequence
247 | of potentially async callbacks to a given input value, returning a promise.
248 | The promise will resolve to the final callback’s result.
249 |
250 | ```js
251 | Function.pipeAsync(input, ...fns);
252 |
253 | const { pipeAsync } = Function;
254 |
255 | // Promise.resolve(5).then(f0).then(f1).then(f2).
256 | pipeAsync(5, f0, f1, f2);
257 |
258 | // Promise.resolve(5).
259 | pipeAsync(5);
260 |
261 | // Promise.resolve(undefined).
262 | pipeAsync();
263 | ```
264 |
265 | The input is first `await`ed.
266 | Then the first callback is applied to `input` and then `await`ed,
267 | then the second callback is applied to the first callback’s result then `await`ed,
268 | and so forth.
269 | In other words, function piping occurs from left to right.
270 |
271 | Each callback is expected to be a unary function.
272 |
273 | If any callback returns a promise that then rejects with an error,
274 | then the promise returned by `Function.pipeAsync` will reject with the same error.
275 |
276 | If `Function.pipeAsync` receives only one argument,
277 | then it will return `Promise.resolve(input)` by default.\
278 | If `Function.pipeAsync` receives no arguments,
279 | then it will return `Promise.resolve(undefined)`.
280 |
281 | ## Function.constant
282 | The `Function.constant` static method creates a new function from a constant value.
283 | The new function will always return that value, no matter what arguments it is given.
284 |
285 | ```js
286 | Function.constant(value);
287 |
288 | const { constant } = Function;
289 |
290 | const f = constant(5);
291 | f(11, 0, 3); // 5.
292 |
293 | const g = constant();
294 | g(11, 0, 3); // undefined.
295 | ```
296 |
297 | The following real-world examples originally used [lodash.constant][].
298 |
299 | ```js
300 | // From cypress@8.6.0/packages/net-stubbing/lib/server/util.ts:
301 | setDefaultHeader('access-control-expose-headers', constant('*'))
302 |
303 | // From cypress@8.6.0/packages/driver/src/cypress/utils.ts:
304 | return [fn, constant(type)]
305 |
306 | // From Odoo v15.0 addons/pad/static/src/js/pad.js:
307 | url.toJSON = constant(this.url);
308 |
309 | // From ng-table@3.0.1/test/specs/settings.spec.ts:
310 | const newSettings: = {
311 | filterOptions: _.mapValues(allSettings.filterOptions, constant(undefined)),
312 | dataOptions: _.mapValues(allSettings.dataOptions, constant(undefined)),
313 | groupOptions: _.mapValues(allSettings.groupOptions, constant(undefined))
314 | };
315 |
316 | // From Elastic Kibana v7.15.1
317 | // src/plugins/vis_types/vislib/public/fixtures/mock_data/histogram/_slices.js.
318 | {
319 | name: 0,
320 | size: 378611,
321 | aggConfig: {
322 | type: 'histogram',
323 | schema: 'segment',
324 | fieldFormatter: constant(String),
325 | params: {
326 | interval: 1000,
327 | extended_bounds: { /* … */ },
328 | },
329 | },
330 | /* … */
331 | },
332 |
333 | // From Yhat Rodeo v2.5.2 src/node/services/files.test.js:
334 | fs.lstat.onCall(0).yields(null, {isDirectory: constant(true)});
335 | ```
336 |
337 | Precedents include:
338 | * [lodash][]: [lodash.constant][] is individually downloaded from NPM
339 | about [81,000 times weekly][lodash.constant]
340 | * [stdlib][]: `import constantFunction from '@stdlib/utils-constant-function';`
341 | * [fp-ts][]: `import { constant } from 'fp-ts/function';`
342 | * [Ramda][]: `import { always } from 'ramda/src/always';`
343 |
344 | [lodash.constant]: https://www.npmjs.com/package/lodash.constant
345 |
346 | ## Function.identity
347 | The `Function.identity` static method always returns its first argument.
348 |
349 | ```js
350 | Function.identity(value);
351 |
352 | const { identity } = Function;
353 |
354 | identity(5); // 5.
355 | identity(); // undefined.
356 | ```
357 |
358 | The following real-world examples originally used [lodash.identity][].
359 |
360 | ```js
361 | // From cypress@8.6.0/packages/driver/src/cypress/runner.ts:
362 | // “Iterates over a suite's tests (including nested suites)
363 | // and will return as soon as the callback is true”.
364 | const findTestInSuite = (suite, fn = identity) => {
365 | for (const test of suite.tests) {
366 | if (fn(test)) {
367 | return test
368 | } /* … */
369 | }
370 | }
371 |
372 | // From gatsby@3.14.3/packages/gatsby-plugin-sharp/src/plugin-options.js:
373 | // “Get all non falsey values”.
374 | return _.pickBy(options, identity)
375 |
376 | // From gatsby@3.14.3/packages/gatsby-plugin-gatsby-cloud/src/constants.js:
377 | export const DEFAULT_OPTIONS = {
378 | // “Optional transform for manipulating headers for sorting, etc”.
379 | transformHeaders: identity,
380 | /* … */
381 | }
382 |
383 | // From ghost@4.19.0/core/frontend/helpers/img_url.js:
384 | // “only make paths relative if we didn't get a request for an absolute url”.
385 | const maybeEnsureRelativePath = !absoluteUrlRequested ? ensureRelativePath : _.identity;
386 |
387 | // From Meteor v2.5.0 tools/cordova/builder.js:
388 | const boilerplate = new Boilerplate(CORDOVA_ARCH, manifest, {
389 | urlMapper: identity,
390 | /* … */
391 | });
392 | ```
393 |
394 | Precedents include:
395 | * [lodash][]: [lodash.identity][] is individually downloaded from NPM
396 | about [700,000 times weekly][lodash.identity]
397 | * [stdlib][]: `import identity from '@stdlib/utils-identity-function';`
398 | * [fp-ts][]: `import { identity } from 'fp-ts/function';`
399 | * [Ramda][]: `import { identity } from 'ramda/src/identity';`
400 |
401 | [lodash.identity]: https://www.npmjs.com/package/lodash.identity
402 |
403 | ## Function.noop
404 |
405 | The `Function.noop` static method always returns undefined.
406 | `Function.noop` is equivalent to `() => {}`.
407 | It is also equivalent to `constant()`.
408 |
409 | This function is already available and frequently used from both [jQuery][] and Lodash, generally to fill a required callback argument or to disable a callback property.
410 |
411 | ```js
412 | const { noop } = Function;
413 | [ 0, 1 ].map(noop)
414 | // [ undefined, undefined ].
415 | ```
416 |
417 | The following real-world examples originally used
418 | jQuery’s [`$.noop`][] or [lodash.noop][].
419 |
420 | ```js
421 | // From Wordpress v5.1.11:
422 | { /* … */
423 | defaultExpandedArguments: {
424 | duration: 'fast',
425 | completeCallback: noop }
426 | /* … */ }
427 |
428 | // From three@0.133.1/test/benchmark/benchmark.js:
429 | SuiteUI.prototype.run = function() {
430 | this.runButton.click = noop;
431 | this.runButton.innerText = "Running..."
432 | this.suite.run({ async: true });
433 | }
434 |
435 | // From typeahead.js@0.11.1/src/typeahead/dataset.js:
436 | this.cancel = function cancel() {
437 | canceled = true;
438 | that.cancel = noop;
439 | that.async &&
440 | that.trigger('asyncCanceled', query);
441 | };
442 |
443 | // From typeahead.js@0.11.1/src/bloodhound/bloodhound.js:
444 | // “if max size is less than 0, provide a noop cache”.
445 | sync = sync || noop;
446 | async = async || noop;
447 | sync(this.remote ? local.slice() : local);
448 |
449 | // From typeahead.js@0.11.1/src/bloodhound/lru_cache.js:
450 | // “if max size is less than 0, provide a noop cache”.
451 | if (this.maxSize <= 0) {
452 | this.set = this.get = $.noop;
453 | }
454 |
455 | // From verdaccio@5.1.6/packages/middleware/src/middleware.ts:
456 | errorReportingMiddleware(req, res, noop);
457 |
458 | // From Odoo v15.0 addons/bus/static/src/js/services/bus_service.js:
459 | Promise.resolve(this._audio.play()).catch(noop);
460 |
461 | // From ClickHouse v21.10.2.15-stable website/js/docsearch.js:
462 | if (this.$hint.length === 0) {
463 | this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = noop;
464 | }
465 | ```
466 |
467 | Precedents include:
468 | * [jQuery][]: [`$.noop`][]
469 | * [lodash][]: [lodash.noop][] is individually downloaded from NPM
470 | about [415,600 times weekly][lodash.noop]
471 |
472 | [`$.noop`]: https://www.npmjs.com/package/lodash.noop
473 | [lodash.noop]: https://www.npmjs.com/package/lodash.noop
474 |
475 | ## Function.prototype.once
476 | The `Function.prototype.once` method creates a new function
477 | that calls the original function at most once,
478 | no matter how much the new function is called.
479 | ```js
480 | fn.once();
481 |
482 | const fn = console.log.once();
483 | fn(5); // Prints 5.
484 | fn(5); // Does not print anything.
485 | fn(5); // Does not print anything.
486 |
487 | const initialize = createApplication.once();
488 | initialize();
489 | initialize();
490 | // createApplication is invoked only once.
491 | ```
492 |
493 | The following real-world example originally used [lodash.once][].
494 |
495 | ```js
496 | // From Meteor v2.2.1:
497 | // “Are we running Meteor from a git checkout?”
498 | export const inCheckout = (function () {
499 | try { /* … */ } catch (e) { console.log(e); }
500 | return false;
501 | }).once();
502 |
503 | // From cypress@8.6.0:
504 | cy.on('command:retry', _.after(2, (() => {
505 | button.remove() /* … */
506 | }).once()))
507 |
508 | // From Jitsi Meet v6482:
509 | this._hangup = (() => {
510 | sendAnalytics(createToolbarEvent('hangup'));
511 | /* … */
512 | }).once()
513 | ```
514 |
515 | Precedents include:
516 | * [lodash][]: [lodash.once][] is individually downloaded from NPM
517 | about [8,900,000 times weekly][lodash.once]
518 | * [jQuery][]: [`.one`][]
519 | * Node: [`eventEmitter.once`][]
520 |
521 | [lodash.once]: https://www.npmjs.com/package/lodash.once
522 | [`.one`]: https://api.jquery.com/one/
523 | [`eventEmitter.once`]: https://nodejs.org/api/events.html#handling-events-only-once
524 |
525 | ## Function.prototype.debounce
526 | The `Function.prototype.debounce` method creates a new function
527 | that calls the original function at most once,
528 | no matter how much the new function is called.
529 | ```js
530 | fn.debounce(numOfMilliseconds);
531 | ```
532 |
533 | Numerous graphical applications use `debounce`.
534 | In this example, logging happens on keyup events from `inputEl`,
535 | but only after the user has stopped typing for at least 250 ms:
536 | ```js
537 | inputEl.addEventListener('keyup',
538 | console.log.debounce(250));
539 | ```
540 |
541 | This method may come with options that could be bikeshedded in Stage 1.
542 |
543 | Precedents include:
544 | * [lodash][]: [lodash.debounce][] is individually downloaded from NPM
545 | about [11,400,000 times weekly][lodash.debounce]
546 |
547 | [lodash.debounce]: https://www.npmjs.com/package/lodash.debounce
548 |
549 | ## Function.prototype.throttle
550 | The `Function.prototype.throttle` method creates a new function that, when called,
551 | calls the original function—but only at most once within a given length of time.
552 | ```js
553 | fn.throttle(numOfMilliseconds);
554 | ```
555 |
556 | Numerous graphical applications use `throttle`.
557 | In this example, logging happens on window scroll, but no more than once every 250 ms:
558 | ```js
559 | inputEl.addEventListener('keyup',
560 | console.log.throttle(250));
561 | ```
562 |
563 | This method may come with options that could be bikeshedded in Stage 1.
564 |
565 | Precedents include:
566 | * [lodash][]: [lodash.throttle][] is individually downloaded from NPM
567 | about [3,700,000 times weekly][lodash.throttle]
568 |
569 | [lodash.throttle]: https://www.npmjs.com/package/lodash.debounce
570 |
571 | ## Function.prototype.aside
572 | The `Function.prototype.aside` method creates a new unary function
573 | that applies some callback to its argument before returning the original argument.
574 |
575 | ```js
576 | fn.aside();
577 |
578 | const { aside } = Function;
579 |
580 | console.log.aside(5); // Prints 5 before returning 5.
581 |
582 | arr.map(console.log.aside).map(f);
583 | // Prints each item from `arr` before passing them to `f`.
584 |
585 | const data = await Promise.resolve('intro.txt')
586 | .then(Deno.open)
587 | .then(Deno.readAll)
588 | .then(console.log.aside())
589 | .then(data => new TextDecoder('utf-8').decode(data));
590 | ```
591 |
592 | The following real-world example originally used [lodash][].aside and lodash/fp’s pipe.
593 |
594 | ```js
595 | // From IBM/report-toolkit v0.6.1 packages/common/src/config.js:
596 | export function filterEnabledRules(config) {
597 | return pipe(
598 | config,
599 | _.getOr({}, 'rules'),
600 | _.toPairs,
601 | _.reduce(
602 | (enabledRules, [ruleName, ruleConfig]) =>
603 | (_.isObject(ruleConfig) && _.get('enabled', ruleConfig)) ||
604 | (_.isBoolean(ruleConfig) && ruleConfig)
605 | ? [ruleName, ...enabledRules]
606 | : enabledRules,
607 | []
608 | ),
609 | (ruleIds => {
610 | debug('found %d enabled rule(s)', ruleIds.length);
611 | }).aside();
612 | }
613 | ```
614 |
615 | Precedents include:
616 | * [lodash][]: `_.tap`
617 | * [Ramda][]: `import { tap } from 'ramda/src/tap';`
618 |
619 | ## Function.prototype.unThis
620 |
621 | The `Function.prototype.unThis` method creates a new function
622 | that calls the original function, supplying its first argument
623 | as the original function’s `this` receiver,
624 | and supplying the rest of its arguments as the original function’s ordinary arguments.
625 |
626 | This is useful for converting `this`-based functions
627 | into non-`this`-based functions.
628 |
629 | ```js
630 | fn.unThis();
631 |
632 | const $slice = Array.prototype.slice.unThis();
633 | $slice([ 0, 1, 2 ], 1); // [ 1, 2 ].
634 | ```
635 |
636 | This is not a substitute for a [bind-this syntax][],
637 | which allows developers to change the receiver of functions
638 | without creating a wrapper function.
639 |
640 | [bind-this syntax]: https://github.com/js-choi/proposal-bind-this
641 |
642 | `fn.unThis()` is equivalent to\
643 | `Function.prototype.call.bind(fn)` and to\
644 | `Function.prototype.bind.bind(Function.prototype.call)(fn)`.
645 |
646 | Therefore, `fn.unThis()(thisArg, ...restArgs)` is equivalent
647 | to `fn.call(thisArg, ...restArgs)`.
648 |
649 | The following real-world example originally used [call-bind][]
650 | or a manually created similar function.
651 |
652 | ```js
653 | // From chrome-devtools-frontend@1.0.934332
654 | // node_modules/array-includes/test/implementation.js.
655 | runTests(implementation.unThis(), t);
656 |
657 | // From string.prototype.trimstart@1.0.4/index.js:
658 | var bound = getPolyfill().unThis();
659 |
660 | // From andreasgal/dom.js (84b7ab6) src/snapshot.js.
661 | const /* … */
662 | join = A.join || Array.prototype.join.unThis(),
663 | map = A.map || Array.prototype.map.unThis(),
664 | push = A.push || Array.prototype.push.unThis(),
665 | /* … */;
666 | ```
667 |
668 | Precedents include:
669 | * [call-bind][]: `callBind`
670 |
671 | [lodash]: https://lodash.com/docs/4.17.15
672 | [stdlib]: https://github.com/stdlib-js/stdlib
673 | [RxJS]: https://rxjs.dev
674 | [fp-ts]: https://gcanti.github.io/fp-ts/
675 | [Ramda]: https://ramdajs.com/
676 | [jQuery]: https://jquery.com/
677 | [call-bind]: https://www.npmjs.com/package/call-bind
678 |
679 | [pipe history]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md
680 |
681 |
682 |
--------------------------------------------------------------------------------