Skip to content

Commit

Permalink
fix!: remove once from Function prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
gretzkiy committed Dec 25, 2024
1 parent 9ce6908 commit 60df4c3
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 93 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ Changelog
_Note: Gaps between patch versions are faulty, broken or test releases._

## v4.0.0-alpha.54 (2024-12-25)

#### :boom: Breaking Change

* Removed `once` from `Function` prototype. Now `once` is a separate wrapper. `core/prelude/function/memoize`
* Renamed exported `once` decorator to `onceDecorator`. `core/functools/memoize`

## v4.0.0-alpha.53 (2024-12-16)

#### :bug: Bug Fix
#### :bug: Bug Fix

* Added handling the rejection of provider in provider request engine `core/request/engines/provider`

Expand Down
4 changes: 2 additions & 2 deletions src/core/async/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export default class Async<CTX extends object = Async<any>> extends Super<CTX> {
wrapper(cb: AnyFunction): unknown {
if (Object.isFunction(originalEmitter)) {
// eslint-disable-next-line func-name-matching
emitter = function wrappedEmitter(this: unknown): CanUndef<Function> {
emitter = function wrappedEmitter(this: unknown): CanUndef<AnyFunction> {
// eslint-disable-next-line prefer-rest-params
const destructor = originalEmitter.apply(this, arguments);

Expand Down Expand Up @@ -182,7 +182,7 @@ export default class Async<CTX extends object = Async<any>> extends Super<CTX> {
};

function handler(this: unknown, ...handlerArgs: unknown[]): unknown {
if (p.single && (hasMultipleEvent || !emitter.once)) {
if (p.single && (hasMultipleEvent || !('once' in emitter))) {
if (hasMultipleEvent) {
that.clearEventListener(ids);

Expand Down
2 changes: 1 addition & 1 deletion src/core/async/events/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export interface EventEmitterLike {
/**
* Extended type of event emitter
*/
export type EventEmitterLikeP = ((event: string, handler: Function) => CanUndef<Function>) | EventEmitterLike;
export type EventEmitterLikeP = ((event: string, handler: AnyFunction) => CanUndef<AnyFunction>) | EventEmitterLike;

export interface AsyncOnOptions<CTX extends object = Async> extends AsyncCbOptionsSingle<CTX> {
/**
Expand Down
7 changes: 7 additions & 0 deletions src/core/functools/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ Changelog
> - :house: [Internal]
> - :nail_care: [Polish]
## v4.0.0-alpha.54 (2024-12-25)

#### :boom: Breaking Change

* Removed `once` from `Function` prototype. Now `once` is a separate wrapper
* Renamed exported `once` decorator to `onceDecorator`

## v4.0.0-alpha.29 (2024-04-04)

#### :bug: Bug Fix
Expand Down
40 changes: 36 additions & 4 deletions src/core/functools/memoize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
*/

/**
* Decorator for `Function.prototype.once`
* Decorator for {@link once}
*
* @decorator
* @see [[Function.once]]
* @see once
*/
export function once(target: object, key: string | symbol, descriptor: PropertyDescriptor): void {
export function onceDecorator(target: object, key: string | symbol, descriptor: PropertyDescriptor): void {
const
method = descriptor.value;

Expand All @@ -23,9 +23,41 @@ export function once(target: object, key: string | symbol, descriptor: PropertyD
descriptor.value = function value(this: object, ...args: unknown[]): unknown {
Object.defineProperty(this, key, {
configurable: true,
value: method.once()
value: once(method)
});

return this[key](...args);
};
}

/**
* Returns a new function that allows to invoke the specified function only once
* @param fn
*/
export function once<T extends AnyFunction>(fn: T): T {
let
called = false,
result;

Object.defineProperty(onceWrapper, 'cancelOnce', {
configurable: true,
enumerable: false,
writable: true,
value: () => {
called = true;
result = undefined;
}
});

return <T>onceWrapper;

function onceWrapper(this: unknown, ...args: unknown[]): unknown {
if (!called) {
result = fn.apply(this, args);
called = true;
}

return result;
}
}

43 changes: 43 additions & 0 deletions src/core/functools/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,51 @@
*/

import { debounce } from 'core/functools/lazy';
import { once } from 'core/functools/memoize';

describe('core/functools', () => {
describe('`once`', () => {
it('should memoize the return result', () => {
const
rand = once(Math.random),
res = rand();

expect(Object.isNumber(res)).toBe(true);
expect(rand()).toBe(res);
expect(rand()).toBe(res);
});

it('should memoize the return result with different arguments', () => {
const testFn = once((i) => i);

expect(testFn(1)).toBe(1);
expect(testFn(2)).toBe(1);
});

it('should not be called multiple times', () => {
const testFn = jest.fn((i) => i);
const onceFn = once(testFn);

onceFn(1);
onceFn(2);
onceFn(3);

expect(testFn).toHaveBeenCalledTimes(1);
});

it('`cancelOnce` should reset return value', () => {
const testFn = jest.fn((i) => i);
const onceFn = once(testFn);

expect(onceFn(1)).toBe(1);
expect(onceFn(2)).toBe(1);

onceFn.cancelOnce();
expect(onceFn(1)).toBe(undefined);
expect(testFn).toBeCalledTimes(1);
});
});

describe('`@debounce`', () => {
it('should decorate a method so it runs delayed by a specified number of ms.', (done) => {
class Input {
Expand Down
6 changes: 6 additions & 0 deletions src/core/prelude/function/memoize/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ Changelog
> - :house: [Internal]
> - :nail_care: [Polish]
## v4.0.0-alpha.54 (2024-12-25)

#### :boom: Breaking Change

* Removed `once` from `Function` prototype. Now `once` is a separate wrapper

## v3.20.0 (2020-07-05)

#### :rocket: New Feature
Expand Down
39 changes: 0 additions & 39 deletions src/core/prelude/function/memoize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,5 @@

import extend from 'core/prelude/extend';

/** @see [[Function.once]] */
extend(Function.prototype, 'once', function once(this: AnyFunction): AnyFunction {
const
// eslint-disable-next-line @typescript-eslint/no-this-alias
fn = this;

let
called = false,
res;

Object.defineProperty(wrapper, 'cancelOnce', {
configurable: true,
enumerable: false,
writable: true,
value: () => {
called = true;
res = undefined;
}
});

return wrapper;

function wrapper(this: unknown, ...args: unknown[]): unknown {
if (called) {
return res;
}

res = fn.apply(this, args);
called = true;
return res;
}
});

/** @see [[Function.cancelOnce]] */
extend(Function.prototype, 'cancelOnce', () => undefined);

/** @see [[FunctionConstructor.once]] */
extend(Function, 'once', (fn: AnyFunction) => fn.once());

/** @see [[FunctionConstructor.cancelOnce]] */
extend(Function, 'cancelOnce', (fn: AnyFunction) => fn.cancelOnce());
35 changes: 0 additions & 35 deletions src/core/prelude/function/memoize/spec.js

This file was deleted.

22 changes: 11 additions & 11 deletions src/core/request/response/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/

import { EventEmitter2 as EventEmitter } from 'eventemitter2';
import { once, deprecated } from 'core/functools';
import { onceDecorator, once, deprecated } from 'core/functools';
import { IS_NODE } from 'core/env';

import { convertIfDate } from 'core/json';
Expand Down Expand Up @@ -253,7 +253,7 @@ export default class Response<
this.headers = Object.freeze(new Headers(p.headers));

if (Object.isFunction(body)) {
this.body = body.once();
this.body = once(body);
this.body[Symbol.asyncIterator] = body[Symbol.asyncIterator].bind(body);

} else {
Expand Down Expand Up @@ -442,7 +442,7 @@ export default class Response<
/**
* Parses the response body as a JSON object and returns it
*/
@once
@onceDecorator
json(): AbortablePromise<JSONLikeValue> {
return this.readBody().then((body) => {
if (body == null) {
Expand Down Expand Up @@ -494,7 +494,7 @@ export default class Response<
/**
* Parses the response data stream as a JSON tokens and yields them via an asynchronous iterator
*/
@once
@onceDecorator
jsonStream(): AsyncIterableIterator<Token> {
const
iter = Parser.from(this.textStream());
Expand All @@ -511,7 +511,7 @@ export default class Response<
/**
* Parses the response body as a FormData object and returns it
*/
@once
@onceDecorator
formData(): AbortablePromise<FormData> {
const
that = this;
Expand Down Expand Up @@ -565,7 +565,7 @@ export default class Response<
/**
* Parses the response body as a Document instance and returns it
*/
@once
@onceDecorator
document(): AbortablePromise<Document> {
return this.readBody().then((body) => {
//#if node_js
Expand Down Expand Up @@ -595,15 +595,15 @@ export default class Response<
/**
* Parses the response body as a string and returns it
*/
@once
@onceDecorator
text(): AbortablePromise<string> {
return this.readBody().then((body) => this.decodeToString(body));
}

/**
* Parses the response data stream as a text chunks and yields them via an asynchronous iterator
*/
@once
@onceDecorator
textStream(): AsyncIterableIterator<string> {
const
iter = this.stream();
Expand All @@ -628,7 +628,7 @@ export default class Response<
/**
* Parses the response data stream as an ArrayBuffer chunks and yields them via an asynchronous iterator
*/
@once
@onceDecorator
stream(): AsyncIterableIterator<ArrayBuffer | undefined> {
const
iter = this[Symbol.asyncIterator]();
Expand All @@ -653,15 +653,15 @@ export default class Response<
/**
* Parses the response body as a Blob structure and returns it
*/
@once
@onceDecorator
blob(): AbortablePromise<Blob> {
return this.readBody().then((body) => this.decodeToBlob(body));
}

/**
* Parses the response body as an ArrayBuffer and returns it
*/
@once
@onceDecorator
arrayBuffer(): AbortablePromise<ArrayBuffer> {
return this.readBody().then((body) => {
if (body == null || body === '') {
Expand Down

0 comments on commit 60df4c3

Please sign in to comment.