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

Suggestion: promise enhancements #169

Closed
sarink opened this issue Feb 5, 2021 · 4 comments · Fixed by #181
Closed

Suggestion: promise enhancements #169

sarink opened this issue Feb 5, 2021 · 4 comments · Fixed by #181

Comments

@sarink
Copy link

sarink commented Feb 5, 2021

I often want to display different text based on the result of the promise (whether success or failure). Sometimes my action is multiple promises, so it would be convenient if the signature for that was () => Promise (so that I don't need to do create a new named variable for my promise on the line before it, and then pass it to ora). Lastly, it seems a lot more convenient to return the promise, rather than the spinner instance.

I've been using this little wrapper

  const oraPromise = async <T>(
    action: () => Promise<T>,
    options: {
      startText?: () => string;
      successText?: (resp: T) => string;
      failText?: (err: Error) => string;
    } = {}
  ): Promise<T> => {
    const spinner = ora();
    const { startText, successText, failText } = options;
    if (startText) spinner.start(startText());
    return action()
      .then((resp) => {
        if (successText) spinner.succeed(successText(resp));
        return resp;
      })
      .catch((err) => {
        if (failText) spinner.fail(failText(err));
        throw err;
      });
  };

I think it enhances the usability of promise for some common cases. If you think it's worthwhile, I may be able to find time to submit a PR.

example:

  // before
  await Promise.all(
    actions.map((name) => {
      const myAction = doSomeAsyncThing(name);
      const spinner = ora.promise(myAction, `Performing async action ${name}...`);
      myAction
        .then((result) => {
          spinner.succeed(`Success! ${result}`);
        })
        .catch((err) => {
          spinner.fail(`Error ${err}`);
        });
      return myAction;
    })
  );
  // after
  await Promise.all(
    actions.map((name) => {
      return oraPromise(() => doSomeAsyncThing(name), {
        startText: () => `Performing async action ${name}...`,
        successText: (result) => `Success! ${result}`,
        failText: (err) => `Error ${err}`,
      });
    })
  );
@sindresorhus
Copy link
Owner

Sounds like a good idea in general. I agree it would be better if it returned the promise instead of the instance. I'm not sure why it's beneficial for it to accept a promise-returning function over just a promise though.

@vanpipy
Copy link

vanpipy commented Mar 1, 2021

I agree with the suggestion to enhance the ora.promise.

Follow the code:

ora/index.js

Line 394 in 498c40a

module.exports.promise = (action, options) => {

It can work with a single action only and it is not different between using the ora directly. As @sarink said,

ora
  .promise(options)
  .then(actionA(spinner) => {...})
  .then(actionB(spinner) => {...})
  .then(actionC(spinner) => {...})
  .end(succeed, failed)

It is a great and different way to use the async ora.

Emmm... the example as above is a simple flow to use ora, but cannot cover the all about ora. Need time to figure out the usage.

@SrBrahma
Copy link
Contributor

SrBrahma commented Jul 2, 2021

Couldn't ora.promise accept (() => Promise<void>) | Promise? I do that in some of my codes. It check if it's a function, and if so, will automatically call it. Having to wrap the given anonymous function and executing it seems unnecessary.

Edit: Also, I agree. It should return a Promise so I can await it.

Edit2: Here is my implementation of oraPromise, based on the @sarink code:

type OraActionType<T> = Promise<T> | ((spinner: ora.Ora) => Promise<T>);
type OraPromiseOptions<T> = {
  /** The new text of the spinner when the promise is resolved.
   *
   * * If undefined, will keep the initial text. */
  successText?: ((resp: T) => string) | string;
  /** The new text of the spinner when the promise is rejected.
   *
   * If undefined, will keep the initial text. */
  failText?: ((err: Error) => string) | string;
} & ora.Options

export async function oraPromise<T>(action: OraActionType<T>, text: string): Promise<T>;
export async function oraPromise<T>(action: OraActionType<T>, options: OraPromiseOptions<T>): Promise<T>;
export async function oraPromise<T>(
  action: OraActionType<T>, complement: string | OraPromiseOptions<T>,
): Promise<T> {
  const { successText, failText } = typeof complement === 'string'
    ? { successText: undefined, failText: undefined }
    : complement;

  const spinner = ora(complement); // Set the initial string or ora options.
  spinner.start();
  try {
    const promise = typeof action === 'function' ? action(spinner) : action;
    const result = await promise;
    spinner.succeed(
      successText === undefined
        ? undefined
        : (typeof successText === 'string' ? successText : successText(result)),
    );
    return result;
  } catch (err) {
    spinner.fail(
      failText === undefined
        ? undefined
        : (typeof failText === 'string' ? failText : failText(err)),
    );
    throw err;
  }
}

Besides the initial OP functionalities (a little improved), this will call the promise function if one is passed, allows further customizations by using ora.Options, and also allows just passing a initial text by a string as 2nd argument.

Edit3: The OP startText is in my code the default text. Not explicitly present there in the object as a prop, but present in the ora.Options.

Edit4: Added the spinner to the promise function. I probably won't use it, but it isn't bad to have it accessible by the user for further control without having to manually create and stop/succeed the spinner, and also having everything noticeably encapsulated.

I will later make a PR for it as I think it's very good and useful.

@SrBrahma
Copy link
Contributor

SrBrahma commented Jul 3, 2021

Created PR @ #181

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants