diff --git a/index.d.ts b/index.d.ts index 8c5b2c2..62174e6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -133,6 +133,22 @@ declare namespace ora { readonly prefixText?: string | PrefixTextGenerator; } + interface PromiseOptions extends Options { + /** + The new text of the spinner when the promise is resolved. + + If undefined, will keep the initial text. + */ + successText?: string | ((result: T) => string); + + /** + The new text of the spinner when the promise is rejected. + + If undefined, will keep the initial text. + */ + failText?: string | ((error: Error) => string); + } + interface Ora { /** A boolean of whether the instance is currently spinning. @@ -259,19 +275,19 @@ declare const ora: { }, 1000); ``` */ - (options?: ora.Options | string): ora.Ora; + (options?: string | ora.Options): ora.Ora; /** - Starts a spinner for a promise. The spinner is stopped with `.succeed()` if the promise fulfills or with `.fail()` if it rejects. + Starts a spinner for a function or a promise. The spinner is stopped with `.succeed()` if the promise fulfills or with `.fail()` if it rejects. Returns the Promise. @param action - The promise to start the spinner for. @param options - If a string is provided, it is treated as a shortcut for `options.text`. @returns The spinner instance. */ - promise( - action: PromiseLike, - options?: ora.Options | string - ): ora.Ora; + promise( + action: PromiseLike | ((spinner: ora.Ora) => PromiseLike), + options?: string | ora.PromiseOptions + ): Promise; }; export = ora; diff --git a/index.js b/index.js index fca892b..af99dff 100644 --- a/index.js +++ b/index.js @@ -393,23 +393,35 @@ const oraFactory = function (options) { module.exports = oraFactory; -module.exports.promise = (action, options) => { +// https://github.com/sindresorhus/ora/issues/169#issuecomment-873269524 +module.exports.promise = async (action, options) => { + const actionIsFunction = typeof action === 'function'; // eslint-disable-next-line promise/prefer-await-to-then - if (typeof action.then !== 'function') { - throw new TypeError('Parameter `action` must be a Promise'); - } + const actionIsPromise = typeof action.then === 'function'; - const spinner = new Ora(options); - spinner.start(); + if (!actionIsFunction && !actionIsPromise) { + throw new TypeError('Parameter `action` must be a Function or a Promise'); + } - (async () => { - try { - await action; - spinner.succeed(); - } catch { - spinner.fail(); - } - })(); + const {successText, failText} = typeof options === 'object' ? + options : + {successText: undefined, failText: undefined}; - return spinner; + const spinner = new Ora(options); // Set the initial string or ora options. + spinner.start(); + try { + const promise = actionIsFunction ? action(spinner) : action; + const result = await promise; + spinner.succeed(successText === undefined ? + undefined : + (typeof successText === 'string' ? successText : successText(result)) + ); + return result; + } catch (error) { + spinner.fail(failText === undefined ? + undefined : + (typeof failText === 'string' ? failText : failText(error)) + ); + throw error; + } }; diff --git a/index.test-d.ts b/index.test-d.ts index 7c74fe1..f238b8a 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -45,11 +45,27 @@ spinner.render(); spinner.frame(); const resolves = Promise.resolve(1); -promise(resolves, 'foo'); -promise(resolves, { +void promise(resolves, 'foo'); +void promise(resolves, { stream: new PassThroughStream(), text: 'foo', color: 'blue', isEnabled: true, isSilent: false }); +void promise(async () => { + await resolves; +}, 'foo'); +void promise(async spinner => { + spinner.prefixText = 'foo'; + await resolves; + return 7; +}, { + stream: new PassThroughStream(), + text: 'foo', + color: 'blue', + isEnabled: true, + isSilent: false, + successText: result => `Resolved with number ${result}`, + failText: 'bar' +}); diff --git a/readme.md b/readme.md index d5687e2..ae4cd52 100644 --- a/readme.md +++ b/readme.md @@ -226,11 +226,28 @@ Change the spinner indent. ### ora.promise(action, text) ### ora.promise(action, options) -Starts a spinner for a promise. The spinner is stopped with `.succeed()` if the promise fulfills or with `.fail()` if it rejects. Returns the spinner instance. +Starts a spinner for a function or a promise. The spinner is stopped with `.succeed()` if the promise fulfills or with `.fail()` if it rejects. Returns the promise. #### action -Type: `Promise` +Type: `Promise | ((spinner: ora.Ora) => Promise)` + +#### options + +Type: `object` + +All of the [options](#options) plus the following: + +##### successText +Type: `string | ((result: T) => string)` + +The new text of the spinner when the promise is resolved. If undefined, will keep the initial text. + +##### failText +Type: `string | ((error: Error) => string)` + +The new text of the spinner when the promise is rejected. If undefined, will keep the initial text. + ## FAQ diff --git a/test.js b/test.js index a2a87d9..6307c5b 100644 --- a/test.js +++ b/test.js @@ -169,15 +169,13 @@ test('.promise() - rejects', async t => { const output = getStream(stream); const rejects = Promise.reject(new Error()); // eslint-disable-line unicorn/error-message - Ora.promise(rejects, { - stream, - text: 'foo', - color: false, - isEnabled: true - }); - try { - await rejects; + await Ora.promise(rejects, { + stream, + text: 'foo', + color: false, + isEnabled: true + }) } catch {} stream.end();