Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(debounceAsync): add async debounce function support #3343

Merged
merged 1 commit into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ const debounced = debounce(
wait = 250, // milliseconds
{
immediate = false, // execute the debounceFunc on the leading end
instance = null // function or class instance for "this"
} = {},
)

Expand All @@ -255,6 +254,35 @@ debounced({ foo: 'bar'}) // delay the execution – again
debounced.cancel() // optional, cancel the execution
```

Async example:

```js
import { debounceAsync } from '@dnb/eufemia/shared/helpers'

async function debounceFunc({ foo }) {
// optionally, add a cancel event (wasCanceled is a "function" to check later if it was canceled)
const wasCanceled = this.addCancelEvent(myCancelMethod)

await wait(1000) // do something async
}

const myCancelMethod = () => {
console.log('canceled')
}

const debounced = debounceAsync(
debounceFunc,
(wait = 250), // milliseconds
)

debounceAsync({ foo: 'bar' }) // delay the execution – again

debounced.cancel() // optional, cancel the execution
debounced.addCancelEvent(myCancelMethod) // alternatively, you can add the cancel event like so
```

In order to use `this.addCancelEvent` you need to use a `function()`, and not an arrow function.

### copyToClipboard

Copies the given string to the device's clipboard.
Expand Down
102 changes: 1 addition & 101 deletions packages/dnb-eufemia/src/shared/__tests__/helpers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
hasSelectedText,
getSelectedText,
insertElementBeforeSelection,
debounce,
isEdge,
isiOS,
isSafari,
Expand All @@ -25,7 +24,7 @@ import {
warn,
} from '../helpers'

import { mockGetSelection, wait } from '../../core/jest/jestSetup'
import { mockGetSelection } from '../../core/jest/jestSetup'

// make it possible to change the navigator lang
// because "navigator.language" defaults to en-GB
Expand Down Expand Up @@ -274,105 +273,6 @@ describe('selection related methods', () => {
})
})

describe('"debounce" should', () => {
it('delay execution', async () => {
let outside = 'one'

const debounced = debounce(({ inside }) => {
outside = inside
expect(outside).toBe('two')

return 'not accessible'
}, 1)

await wait(2)

const result = debounced({ inside: 'two' })

expect(typeof debounced).toBe('function')
expect(typeof debounced.cancel).toBe('function')

expect(outside).toBe('one')
expect(result).toBe(undefined)
})

it('use given instance', async () => {
const instance = () => {}
instance.property = 'hello'

const debounced = debounce(
// Needs to be a function (so we can use "this")
function () {
expect(this).toBe(instance)
expect(this.property).toBe(instance.property)
},
1,
{ instance }
)

debounced()
})

it('execution immediate', async () => {
let outside = 'one'

const debounced = debounce(
({ inside }) => {
expect(outside).toBe('one')
outside = inside
expect(outside).toBe('two')
},
1,
{ immediate: true }
)

debounced({ inside: 'two' })

await wait(2)

expect(outside).toBe('two')
})

it('execution immediate and return result', async () => {
let outside = 'one'

const debounced = debounce(
({ inside }) => {
expect(outside).toBe('one')
outside = inside
expect(outside).toBe('two')

return inside
},
1,
{ immediate: true }
)

const immediateResult = debounced({ inside: 'two' })

await wait(2)

expect(outside).toBe('two')
expect(immediateResult).toBe('two')
})

it('should not run debounced function when cancelled', async () => {
let outside = 'one'

const debounced = debounce(({ inside }) => {
expect(outside).toBe('one')
outside = inside
expect(outside).toBe('two')
}, 1)
debounced({ inside: 'two' })
debounced.cancel()

await wait(2)

expect(outside).toBe('one')
})
})

describe('"warn" should', () => {
const log = global.console.log

Expand Down
83 changes: 3 additions & 80 deletions packages/dnb-eufemia/src/shared/helpers.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/**
* Global helpers
*
* NB: Do not import other deps in this file.
* Just to have things clean and one directional.
*/

// For backward compatibility
export { debounce, debounceAsync } from './helpers/debounce'

export const PLATFORM_MAC = 'Mac|iPad|iPhone|iPod'
export const PLATFORM_WIN = 'Win'
export const PLATFORM_ANDROID = 'Android'
Expand Down Expand Up @@ -235,84 +236,6 @@ export function scrollToLocationHashId({
}
}

/**
* More or less classical debounce function
*
* Calling this function will return a new function, that, as long as it continues to be invoked, will not
* be triggered. The function will be called after it stops being called for
* N milliseconds. If `immediate` is passed, trigger the function on the
* leading edge, instead of the trailing.
*
* @param {function} func The function to execute
* @param {number} wait The time (milliseconds) before the first given function executes, if the returned one, not got called
* @param {object} options The options object
* @property {boolean} immediate If true, the function will execute immediately. Defaults to false
* @property {instance} instance Defines the instance "this" to use on the executed function
* @returns The function to invalidate the execution
*/
export function debounce(
func,
wait = 250,
{ immediate = false, instance = null } = {}
) {
let timeout
let recall

const cancel = () => clearTimeout(timeout)

// This is the function that is actually executed when
// the DOM event is triggered.
function executedFunction(...args) {
// Store the instance of this and any
// parameters passed to executedFunction
let inst = this

// console.log('instance', instance)

if (typeof recall === 'function') {
recall()
}

// The function to be called after
// the debounce time has elapsed
const later = () => {
// null timeout to indicate the debounce ended
timeout = null

// Call function now if you did not on the leading end
if (!immediate) {
recall = func.apply(instance || inst, args)
}
}

// Determine if you should call the function
// on the leading or trail end
const callNow = immediate && !timeout

// This will reset the waiting every function execution.
// This is the step that prevents the function from
// being executed because it will never reach the
// inside of the previous setTimeout
clearTimeout(timeout)

// Restart the debounce waiting period.
// setTimeout returns a truthy value (it differs in web vs node)
timeout = setTimeout(later, wait)

// Call immediately if you're dong a leading
// end execution
if (callNow) {
recall = func.apply(instance || inst, args)
}

return recall
}

executedFunction.cancel = cancel

return executedFunction
}

export function insertElementBeforeSelection(elem) {
try {
const selection = window.getSelection()
Expand Down
Loading
Loading