From fba496bba2fb5e8c60a0a7a47eb21c5a2ea1fce2 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 4 Nov 2021 00:30:51 +0700 Subject: [PATCH] Meta tweaks --- .github/funding.yml | 4 - .github/workflows/main.yml | 2 - example.js | 2 +- index.d.ts | 117 ++++++++++++------------- index.js | 17 ++-- index.test-d.ts | 172 ++++++++++++++++++------------------- package.json | 21 +---- readme.md | 8 +- test.js | 50 ++++++----- 9 files changed, 186 insertions(+), 207 deletions(-) delete mode 100644 .github/funding.yml diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index 15edf6e..0000000 --- a/.github/funding.yml +++ /dev/null @@ -1,4 +0,0 @@ -github: sindresorhus -open_collective: sindresorhus -patreon: sindresorhus -custom: https://sindresorhus.com/donate diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3b8aa86..441975c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,8 +11,6 @@ jobs: matrix: node-version: - 16 - - 14 - - 12 steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/example.js b/example.js index e986893..162838a 100644 --- a/example.js +++ b/example.js @@ -16,7 +16,7 @@ const allProgressPromise = PProgress.all([ delay(103), progressPromise(), delay(55), - delay(209) + delay(209), ]); allProgressPromise.onProgress(console.log); diff --git a/index.d.ts b/index.d.ts index 2f86a55..9bbbc55 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,3 +1,4 @@ +// TODO: Use the built-in type when TS 4.5 is out. type Awaited = ValueType extends undefined ? ValueType : ValueType extends PromiseLike ? ResolveValueType : ValueType; // https://github.com/microsoft/TypeScript/blob/582e404a1041ce95d22939b73f0b4d95be77c6ec/lib/lib.es2020.promise.d.ts#L21-L31 @@ -11,7 +12,7 @@ export type PromiseSettledResult = { export interface Options { /** - Number of concurrently pending promises. Minimum: `1`. + The number of concurrently pending promises. Minimum: `1`. To run the promises in series, set it to `1`. @@ -27,65 +28,11 @@ export type PromiseFactory = () => PromiseLike; export type ProgressNotifier = (progress: number) => void; // @ts-expect-error `Promise.all` currently uses an incompatible combinatorics-based type definition (https://github.com/microsoft/TypeScript/issues/39788) -export class PProgress extends Promise { - /** - The current progress percentage of the promise as a number between 0 and 1. - */ - readonly progress: number; - - /** - Same as the [`Promise` constructor](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise). - - @param executor - Same as the `Promise` constructor but with an appended `progress` parameter in `executor`. - - @example - ``` - import {PProgress} from 'p-progress'; - - const progressPromise = new PProgress((resolve, reject, progress) => { - const job = new Job(); - - job.on('data', data => { - progress(data.length / job.totalSize); - }); - - job.on('finish', resolve); - job.on('error', reject); - }); - - progressPromise.onProgress(progress => { - console.log(`${progress * 100}%`); - //=> 9% - //=> 23% - //=> 59% - //=> 75% - //=> 100% - }); - - await progressPromise; - ``` - */ - constructor( - /** - @param progress - Call this with progress updates. It expects a number between 0 and 1. - - Multiple calls with the same number will result in only one `onProgress()` event. - - Calling with a number lower than previously will be ignored. - - Progress percentage `1` is reported for you when the promise resolves. If you set it yourself, it will simply be ignored. - */ - executor: ( - resolve: (value?: ValueType | PromiseLike) => void, - reject: (reason?: unknown) => void, - progress: ProgressNotifier - ) => void - ); - +export class PProgress extends Promise { // eslint-disable-line @typescript-eslint/naming-convention /** Convenience method to run multiple promises and get a total progress of all of them. It counts normal promises with progress `0` when pending and progress `1` when resolved. For `PProgress` type promises, it listens to their `onProgress()` method for more fine grained progress reporting. You can mix and match normal promises and `PProgress` promises. - @param promises - Array of promises or promise-returning functions, similar to [p-all](https://github.com/sindresorhus/p-all). + @param promises - Promises or promise-returning functions, similar to [p-all](https://github.com/sindresorhus/p-all). @example ``` @@ -145,7 +92,7 @@ export class PProgress extends Promise { /** Like [`Promise.allSettled`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) but also exposes the total progress of all of the promises like `PProgress.all`. - @param promises - Array of promises or promise-returning functions, similar to [p-all](https://github.com/sindresorhus/p-all). + @param promises - Promises or promise-returning functions, similar to [p-all](https://github.com/sindresorhus/p-all). @example ``` @@ -211,6 +158,60 @@ export class PProgress extends Promise { options?: Options ): PProgress>>; + /** + The current progress percentage of the promise as a number between 0 and 1. + */ + readonly progress: number; + + /** + Same as the [`Promise` constructor](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise). + + @param executor - Same as the `Promise` constructor but with an appended `progress` parameter in `executor`. + + @example + ``` + import {PProgress} from 'p-progress'; + + const progressPromise = new PProgress((resolve, reject, progress) => { + const job = new Job(); + + job.on('data', data => { + progress(data.length / job.totalSize); + }); + + job.on('finish', resolve); + job.on('error', reject); + }); + + progressPromise.onProgress(progress => { + console.log(`${progress * 100}%`); + //=> 9% + //=> 23% + //=> 59% + //=> 75% + //=> 100% + }); + + await progressPromise; + ``` + */ + constructor( + /** + @param progress - Call this with progress updates. It expects a number between 0 and 1. + + Multiple calls with the same number will result in only one `onProgress()` event. + + Calling with a number lower than previously will be ignored. + + Progress percentage `1` is reported for you when the promise resolves. If you set it yourself, it will simply be ignored. + */ + executor: ( + resolve: (value?: ValueType | PromiseLike) => void, + reject: (reason?: unknown) => void, + progress: ProgressNotifier + ) => void + ); + /** Accepts a function that gets `instance.progress` as an argument and is called for every progress event. */ diff --git a/index.js b/index.js index 94194fc..701decb 100644 --- a/index.js +++ b/index.js @@ -14,8 +14,8 @@ export class PProgress extends Promise { static all(promises, options) { return pProgress(async progress => { if ( - options && typeof options.concurrency === 'number' && - !(promises.every(promise => typeof promise === 'function')) + options && typeof options.concurrency === 'number' + && !(promises.every(promise => typeof promise === 'function')) ) { throw new TypeError('When `options.concurrency` is set, the first argument must be an Array of Promise-returning functions'); } @@ -51,8 +51,8 @@ export class PProgress extends Promise { static allSettled(promises, {concurrency} = {}) { return pProgress(async progress => { if ( - typeof concurrency === 'number' && - !(promises.every(promise => typeof promise === 'function')) + typeof concurrency === 'number' + && !(promises.every(promise => typeof promise === 'function')) ) { throw new TypeError('When `options.concurrency` is set, the first argument must be an Array of Promise-returning functions'); } @@ -78,12 +78,12 @@ export class PProgress extends Promise { try { return { status: 'fulfilled', - value: await promise + value: await promise, }; } catch (error) { return { status: 'rejected', - reason: error + reason: error, }; } finally { progressMap.set(promise, 1); @@ -92,7 +92,7 @@ export class PProgress extends Promise { }; return pTimes(promises.length, mapper, { - concurrency + concurrency, }); }); } @@ -133,7 +133,7 @@ export class PProgress extends Promise { if (progress !== 1) { setProgress(progress); } - } + }, ); }); @@ -156,7 +156,6 @@ export class PProgress extends Promise { } then(onFulfilled, onRejected) { - // eslint-disable-next-line promise/prefer-await-to-then const child = super.then(onFulfilled, onRejected); this._listeners.add(progress => { child._setProgress(progress); diff --git a/index.test-d.ts b/index.test-d.ts index ba69303..06924ae 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -15,73 +15,73 @@ expectType>( pProgress(async (progress: ProgressNotifier) => { expectType<(progress: number) => void>(progress); return true; - }) + }), ); // PProgress.all expectType>( - PProgress.all([Promise.resolve('sindresorhus.com')]) + PProgress.all([Promise.resolve('sindresorhus.com')]), ); expectType>( - PProgress.all([async () => Promise.resolve('sindresorhus.com')]) + PProgress.all([async () => Promise.resolve('sindresorhus.com')]), ); expectType>( - PProgress.all([async () => Promise.resolve('sindresorhus.com')], {concurrency: 1}) + PProgress.all([async () => Promise.resolve('sindresorhus.com')], {concurrency: 1}), ); expectType>( - PProgress.all([Promise.resolve('sindresorhus.com'), Promise.resolve(1)]) + PProgress.all([Promise.resolve('sindresorhus.com'), Promise.resolve(1)]), ); expectType>( PProgress.all([ async () => Promise.resolve('sindresorhus.com'), - async () => Promise.resolve(1) - ]) + async () => Promise.resolve(1), + ]), ); expectType>( PProgress.all( [async () => Promise.resolve('sindresorhus.com'), async () => Promise.resolve(1)], - {concurrency: 1} - ) + {concurrency: 1}, + ), ); expectType>( PProgress.all([ Promise.resolve('sindresorhus.com'), Promise.resolve(1), - Promise.resolve(true) - ]) + Promise.resolve(true), + ]), ); expectType>( PProgress.all([ async () => Promise.resolve('sindresorhus.com'), async () => Promise.resolve(1), - async () => Promise.resolve(true) - ]) + async () => Promise.resolve(true), + ]), ); expectType>( PProgress.all( [ async () => Promise.resolve('sindresorhus.com'), async () => Promise.resolve(1), - async () => Promise.resolve(true) + async () => Promise.resolve(true), ], - {concurrency: 1} - ) + {concurrency: 1}, + ), ); expectType>( PProgress.all([ Promise.resolve('sindresorhus.com'), Promise.resolve(1), Promise.resolve(true), - Promise.resolve(Symbol('Test')) - ]) + Promise.resolve(Symbol('Test')), + ]), ); expectType>( PProgress.all([ async () => Promise.resolve('sindresorhus.com'), async () => Promise.resolve(1), async () => Promise.resolve(true), - async () => Promise.resolve(Symbol('Test')) - ]) + async () => Promise.resolve(Symbol('Test')), + ]), ); expectType>( PProgress.all( @@ -89,10 +89,10 @@ expectType>( async () => Promise.resolve('sindresorhus.com'), async () => Promise.resolve(1), async () => Promise.resolve(true), - async () => Promise.resolve(Symbol('Test')) + async () => Promise.resolve(Symbol('Test')), ], - {concurrency: 1} - ) + {concurrency: 1}, + ), ); expectType>( PProgress.all([ @@ -100,8 +100,8 @@ expectType>( Promise.resolve(1), Promise.resolve(true), Promise.resolve(Symbol('Test')), - Promise.resolve(['foo']) - ]) + Promise.resolve(['foo']), + ]), ); expectType>( PProgress.all([ @@ -109,8 +109,8 @@ expectType>( async () => Promise.resolve(1), async () => Promise.resolve(true), async () => Promise.resolve(Symbol('Test')), - async () => Promise.resolve(['foo']) - ]) + async () => Promise.resolve(['foo']), + ]), ); expectType>( PProgress.all( @@ -119,10 +119,10 @@ expectType>( async () => Promise.resolve(1), async () => Promise.resolve(true), async () => Promise.resolve(Symbol('Test')), - async () => Promise.resolve(['foo']) + async () => Promise.resolve(['foo']), ], - {concurrency: 1} - ) + {concurrency: 1}, + ), ); expectType>( PProgress.all([ @@ -131,8 +131,8 @@ expectType>( Promise.resolve(true), Promise.resolve(Symbol('Test')), Promise.resolve(['foo']), - Promise.resolve('sindresorhus.com') - ]) + Promise.resolve('sindresorhus.com'), + ]), ); expectType>( PProgress.all([ @@ -141,8 +141,8 @@ expectType>( async () => Promise.resolve(true), async () => Promise.resolve(Symbol('Test')), async () => Promise.resolve(['foo']), - async () => Promise.resolve('sindresorhus.com') - ]) + async () => Promise.resolve('sindresorhus.com'), + ]), ); expectType>( PProgress.all( @@ -152,10 +152,10 @@ expectType>( async () => Promise.resolve(true), async () => Promise.resolve(Symbol('Test')), async () => Promise.resolve(['foo']), - async () => Promise.resolve('sindresorhus.com') + async () => Promise.resolve('sindresorhus.com'), ], - {concurrency: 1} - ) + {concurrency: 1}, + ), ); expectType< PProgress<[string, number, boolean, symbol, string[], string, number]> @@ -167,8 +167,8 @@ PProgress<[string, number, boolean, symbol, string[], string, number]> Promise.resolve(Symbol('Test')), Promise.resolve(['foo']), Promise.resolve('sindresorhus.com'), - Promise.resolve(1) - ]) + Promise.resolve(1), + ]), ); expectType< PProgress<[string, number, boolean, symbol, string[], string, number]> @@ -180,8 +180,8 @@ PProgress<[string, number, boolean, symbol, string[], string, number]> async () => Promise.resolve(Symbol('Test')), async () => Promise.resolve(['foo']), async () => Promise.resolve('sindresorhus.com'), - async () => Promise.resolve(1) - ]) + async () => Promise.resolve(1), + ]), ); expectType< PProgress<[string, number, boolean, symbol, string[], string, number]> @@ -194,10 +194,10 @@ PProgress<[string, number, boolean, symbol, string[], string, number]> async () => Promise.resolve(Symbol('Test')), async () => Promise.resolve(['foo']), async () => Promise.resolve('sindresorhus.com'), - async () => Promise.resolve(1) + async () => Promise.resolve(1), ], - {concurrency: 1} - ) + {concurrency: 1}, + ), ); expectType< PProgress< @@ -212,8 +212,8 @@ PProgress< Promise.resolve(['foo']), Promise.resolve('sindresorhus.com'), Promise.resolve(1), - Promise.resolve(true) - ]) + Promise.resolve(true), + ]), ); expectType< PProgress< @@ -228,8 +228,8 @@ PProgress< async () => Promise.resolve(['foo']), async () => Promise.resolve('sindresorhus.com'), async () => Promise.resolve(1), - async () => Promise.resolve(true) - ]) + async () => Promise.resolve(true), + ]), ); expectType< PProgress< @@ -245,10 +245,10 @@ PProgress< async () => Promise.resolve(['foo']), async () => Promise.resolve('sindresorhus.com'), async () => Promise.resolve(1), - async () => Promise.resolve(true) + async () => Promise.resolve(true), ], - {concurrency: 1} - ) + {concurrency: 1}, + ), ); expectType< PProgress< @@ -264,8 +264,8 @@ PProgress< Promise.resolve('sindresorhus.com'), Promise.resolve(1), Promise.resolve(true), - Promise.resolve(Symbol('Test')) - ]) + Promise.resolve(Symbol('Test')), + ]), ); expectType< PProgress< @@ -281,8 +281,8 @@ PProgress< async () => Promise.resolve('sindresorhus.com'), async () => Promise.resolve(1), async () => Promise.resolve(true), - async () => Promise.resolve(Symbol('Test')) - ]) + async () => Promise.resolve(Symbol('Test')), + ]), ); expectType< PProgress< @@ -299,10 +299,10 @@ PProgress< async () => Promise.resolve('sindresorhus.com'), async () => Promise.resolve(1), async () => Promise.resolve(true), - async () => Promise.resolve(Symbol('Test')) + async () => Promise.resolve(Symbol('Test')), ], - {concurrency: 1} - ) + {concurrency: 1}, + ), ); expectType< PProgress< @@ -316,7 +316,7 @@ PProgress< number, boolean, symbol, - string[] + string[], ] > >( @@ -330,8 +330,8 @@ PProgress< Promise.resolve(1), Promise.resolve(true), Promise.resolve(Symbol('Test')), - Promise.resolve(['foo']) - ]) + Promise.resolve(['foo']), + ]), ); expectType< PProgress< @@ -345,7 +345,7 @@ PProgress< number, boolean, symbol, - string[] + string[], ] > >( @@ -359,8 +359,8 @@ PProgress< async () => Promise.resolve(1), async () => Promise.resolve(true), async () => Promise.resolve(Symbol('Test')), - async () => Promise.resolve(['foo']) - ]) + async () => Promise.resolve(['foo']), + ]), ); expectType< PProgress< @@ -374,7 +374,7 @@ PProgress< number, boolean, symbol, - string[] + string[], ] > >( @@ -389,10 +389,10 @@ PProgress< async () => Promise.resolve(1), async () => Promise.resolve(true), async () => Promise.resolve(Symbol('Test')), - async () => Promise.resolve(['foo']) + async () => Promise.resolve(['foo']), ], - {concurrency: 1} - ) + {concurrency: 1}, + ), ); expectType>( @@ -407,8 +407,8 @@ expectType>( PProgress.all([ @@ -422,8 +422,8 @@ expectType Promise.resolve(true), async () => Promise.resolve(Symbol('Test')), async () => Promise.resolve(['foo']), - async () => Promise.resolve(['foo']) - ]) + async () => Promise.resolve(['foo']), + ]), ); expectType>( PProgress.all( @@ -438,10 +438,10 @@ expectType Promise.resolve(true), async () => Promise.resolve(Symbol('Test')), async () => Promise.resolve(['foo']), - async () => Promise.resolve(['foo']) + async () => Promise.resolve(['foo']), ], - {concurrency: 1} - ) + {concurrency: 1}, + ), ); expectType>>( @@ -457,9 +457,9 @@ expectType>>( Promise.resolve(true), Promise.resolve(Symbol('Test')), Promise.resolve(['foo']), - Promise.resolve(['foo']) - ]) - ) + Promise.resolve(['foo']), + ]), + ), ); expectType>>( PProgress.all( @@ -474,9 +474,9 @@ expectType>>( async () => Promise.resolve(true), async () => Promise.resolve(Symbol('Test')), async () => Promise.resolve(['foo']), - async () => Promise.resolve(['foo']) - ]) - ) + async () => Promise.resolve(['foo']), + ]), + ), ); expectType>>( PProgress.all( @@ -491,16 +491,16 @@ expectType>>( async () => Promise.resolve(true), async () => Promise.resolve(Symbol('Test')), async () => Promise.resolve(['foo']), - async () => Promise.resolve(['foo']) + async () => Promise.resolve(['foo']), ]), - {concurrency: 1} - ) + {concurrency: 1}, + ), ); expectType< PProgress<[PromiseSettledResult, PromiseSettledResult]> >( PProgress.allSettled([ Promise.resolve('sindresorhus.com'), - Promise.resolve(1) - ]) + Promise.resolve(1), + ]), ); diff --git a/package.json b/package.json index 2fa145d..ffa4108 100644 --- a/package.json +++ b/package.json @@ -34,28 +34,15 @@ "bluebird" ], "dependencies": { - "p-times": "^3.0.0" + "p-times": "^4.0.0" }, "devDependencies": { - "@babel/eslint-parser": "^7.14.4", - "@babel/plugin-syntax-top-level-await": "^7.12.13", "ava": "^3.15.0", "delay": "^5.0.0", "in-range": "^3.0.0", "time-span": "^5.0.0", - "tsd": "^0.16.0", - "typescript": "^4.3.5", - "xo": "^0.40.1" - }, - "xo": { - "parser": "@babel/eslint-parser", - "parserOptions": { - "requireConfigFile": false, - "babelOptions": { - "plugins": [ - "@babel/plugin-syntax-top-level-await" - ] - } - } + "tsd": "^0.18.0", + "typescript": "^4.4.4", + "xo": "^0.46.4" } } diff --git a/readme.md b/readme.md index 714b2d9..47a1852 100644 --- a/readme.md +++ b/readme.md @@ -6,8 +6,8 @@ Useful for reporting progress to the user during long-running async operations. ## Install -``` -$ npm install p-progress +```sh +npm install p-progress ``` ## Usage @@ -158,7 +158,7 @@ console.log(await allProgressPromise); Type: `Promise[]` -Array of promises or promise-returning functions, similar to [p-all](https://github.com/sindresorhus/p-all). +An array of promises or promise-returning functions, similar to [p-all](https://github.com/sindresorhus/p-all). #### options @@ -170,7 +170,7 @@ Type: `number`\ Default: `Infinity`\ Minimum: `1` -Number of concurrently pending promises. +The number of concurrently pending promises. To run the promises in series, set it to `1`. diff --git a/test.js b/test.js index 9b55aec..1880377 100644 --- a/test.js +++ b/test.js @@ -37,7 +37,6 @@ test('new PProgress()', async t => { t.true(progress >= 0 && progress <= 1, `${progress}`); }); - // eslint-disable-next-line promise/prefer-await-to-then promise.then(result => [result, result]).then(results => { t.true(Array.isArray(results)); for (const result of results) { @@ -49,7 +48,6 @@ test('new PProgress()', async t => { t.true(progress >= 0 && progress <= 1, `${progress}`); }); - // eslint-disable-next-line promise/prefer-await-to-then promise.catch(() => {}).onProgress(progress => { t.is(progress, promise.progress); t.true(progress >= 0 && progress <= 1, `${progress}`); @@ -107,7 +105,7 @@ test('PProgress.all()', async t => { delay(55), fixtureFunction2(fixture), delay(14), - delay(209) + delay(209), ]); promise.onProgress(progress => { @@ -120,7 +118,7 @@ test('PProgress.all()', async t => { undefined, fixture, undefined, - undefined + undefined, ]); }); @@ -143,17 +141,17 @@ test('PProgress.all() with concurrency = 1', async t => { // Should throw when first argument is array of promises instead of promise-returning functions await t.throwsAsync(PProgress.all([fixtureFunction(fixture), fixtureFunction2(fixture)], { - concurrency: 1 + concurrency: 1, }), { - instanceOf: TypeError + instanceOf: TypeError, }); const end = timeSpan(); const promise = PProgress.all([ () => fixtureFunction(fixture), - () => fixtureFunction2(fixture) + () => fixtureFunction2(fixture), ], { - concurrency: 1 + concurrency: 1, }); promise.onProgress(progress => { @@ -162,12 +160,12 @@ test('PProgress.all() with concurrency = 1', async t => { t.deepEqual(await promise, [ fixture, - fixture + fixture, ]); t.true(inRange(end(), { start: 200, // 4 delays of 50ms each - end: 300 // Reasonable padding + end: 300, // Reasonable padding })); }); @@ -198,7 +196,7 @@ test('PProgress.allSettled()', async t => { delay(55), fixtureFunction2(errorFixture), delay(14), - delay(209) + delay(209), ]); promise.onProgress(progress => { @@ -208,28 +206,28 @@ test('PProgress.allSettled()', async t => { t.deepEqual(await promise, [ { status: 'fulfilled', - value: undefined + value: undefined, }, { status: 'fulfilled', - value: fixture + value: fixture, }, { status: 'fulfilled', - value: undefined + value: undefined, }, { status: 'rejected', - reason: errorFixture + reason: errorFixture, }, { status: 'fulfilled', - value: undefined + value: undefined, }, { status: 'fulfilled', - value: undefined - } + value: undefined, + }, ]); }); @@ -252,17 +250,17 @@ test('PProgress.allSettled() with concurrency = 1', async t => { // Should throw when first argument is array of promises instead of promise-returning functions await t.throwsAsync(PProgress.allSettled([fixtureFunction(fixture)], { - concurrency: 1 + concurrency: 1, }), { - instanceOf: TypeError + instanceOf: TypeError, }); const end = timeSpan(); const promise = PProgress.allSettled([ () => fixtureFunction(fixture), - () => fixtureFunction2(errorFixture) + () => fixtureFunction2(errorFixture), ], { - concurrency: 1 + concurrency: 1, }); promise.onProgress(progress => { @@ -272,16 +270,16 @@ test('PProgress.allSettled() with concurrency = 1', async t => { t.deepEqual(await promise, [ { status: 'fulfilled', - value: fixture + value: fixture, }, { status: 'rejected', - reason: errorFixture - } + reason: errorFixture, + }, ]); t.true(inRange(end(), { start: 200, // 4 delays of 50ms each - end: 300 // Reasonable padding + end: 300, // Reasonable padding })); });