All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.dv8tion.jda.api.requests.RestAction Maven / Gradle / Ivy

Go to download

Java wrapper for the popular chat & VOIP service: Discord https://discord.com

There is a newer version: 5.1.0
Show newest version
/*
 * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.dv8tion.jda.api.requests;

import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.exceptions.ContextException;
import net.dv8tion.jda.api.exceptions.RateLimitedException;
import net.dv8tion.jda.api.utils.Result;
import net.dv8tion.jda.api.utils.concurrent.DelayedCompletableFuture;
import net.dv8tion.jda.internal.requests.RestActionImpl;
import net.dv8tion.jda.internal.requests.restaction.operator.*;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.ContextRunnable;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * A class representing a terminal between the user and the discord API.
 * 
This is used to offer users the ability to decide how JDA should limit a Request. * *

Methods that return an instance of RestAction require an additional step * to complete the execution. Thus the user needs to append a follow-up method. * *

A default RestAction is issued with the following operations: *

    *
  • {@link #queue()}, {@link #queue(Consumer)}, {@link #queue(Consumer, Consumer)} *
    The fastest and most simplistic way to execute a RestAction is to queue it. *
    This method has two optional callback functions, one with the generic type and another with a failure exception.
  • * *
  • {@link #submit()}, {@link #submit(boolean)} *
    Provides a Future representing the pending request. *
    An optional parameter of type boolean can be passed to disable automated rate limit handling. (not recommended)
  • * *
  • {@link #complete()}, {@link #complete(boolean)} *
    Blocking execution building up on {@link #submit()}. *
    This will simply block the thread and return the Request result, or throw an exception. *
    An optional parameter of type boolean can be passed to disable automated rate limit handling. (not recommended)
  • *
* * The most efficient way to use a RestAction is by using the asynchronous {@link #queue()} operations. *
These allow users to provide success and failure callbacks which will be called at a convenient time. * *

Planning Execution
* To schedule a RestAction we provide both {@link #queue()} and {@link #complete()} versions that * will be executed by a {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} after a * specified delay: *

    *
  • {@link #queueAfter(long, TimeUnit)} *
    Schedules a call to {@link #queue()} with default callback {@link java.util.function.Consumer Consumers} to be executed after the specified {@code delay}. *
    The {@link java.util.concurrent.TimeUnit TimeUnit} is used to convert the provided long into a delay time. *
    Example: {@code queueAfter(1, TimeUnit.SECONDS);} *
    will call {@link #queue()} 1 second later.
  • * *
  • {@link #submitAfter(long, TimeUnit)} *
    This returns a {@link java.util.concurrent.ScheduledFuture ScheduledFuture} which * can be joined into the current Thread using {@link java.util.concurrent.ScheduledFuture#get()} *
    The blocking call to {@code submitAfter(delay, unit).get()} will return * the value processed by a call to {@link #complete()}
  • * *
  • {@link #completeAfter(long, TimeUnit)} *
    This operation simply sleeps for the given delay and will call {@link #complete()} * once finished sleeping.
  • *
* *

All of those operations provide overloads for optional parameters such as a custom * {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} instead of using the default * global JDA executor. Specifically {@link #queueAfter(long, TimeUnit)} has overloads * to provide a success and/or failure callback due to the returned {@link java.util.concurrent.ScheduledFuture ScheduledFuture} * not being able to provide the response values of the {@link #queue()} callbacks. * *

Using RestActions
* The most common way to use a RestAction is not using the returned value. *
For instance sending messages usually means you will not require to view the message once * it was sent. Thus we can simply use the asynchronous {@link #queue()} operation which will * be executed on a rate limit worker thread in the background, without blocking your current thread: *


 *      {@link net.dv8tion.jda.api.entities.channel.middleman.MessageChannel MessageChannel} channel = event.getChannel();
 *     {@literal RestAction} action = channel.sendMessage("Hello World");
 *      action.{@link #queue() queue()}; // Execute the rest action asynchronously
 * 
* *

Sometimes it is important to access the response value, possibly to modify it later. *
Now we have two options to actually access the response value, either using an asynchronous * callback {@link java.util.function.Consumer Consumer} or the (not recommended) {@link #complete()} which will block * the current thread until the response has been processed and joins with the current thread. * *

Example Queue: (recommended)
*


 *     {@link net.dv8tion.jda.api.entities.channel.middleman.MessageChannel MessageChannel} channel = event.getChannel();
 *     final long time = System.currentTimeMillis();
 *    {@literal RestAction} action = channel.sendMessage("Calculating Response Time...");
 *     {@link java.util.function.Consumer Consumer}{@literal } callback = (message) {@literal ->  {
 *        Message m = message; // ^This is a lambda parameter!^
 *        m.editMessage("Response Time: " + (System.currentTimeMillis() - time) + "ms").queue();
 *        // End with queue() to not block the callback thread!
 *      }};
 *     // You can also inline this with the queue parameter: action.queue(m {@literal ->} m.editMessage(...).queue());
 *     action.{@link #queue(Consumer) queue(callback)};
 * 
* *

Example Complete:
*


 *     {@link net.dv8tion.jda.api.entities.channel.middleman.MessageChannel MessageChannel} channel = event.getChannel();
 *     final long time = System.currentTimeMillis();
 *    {@literal RestAction} action = channel.sendMessage("Calculating Response Time...");
 *     Message message = action.{@link #complete() complete()};
 *     message.editMessage("Response Time: " + (System.currentTimeMillis() - time) + "ms").queue();
 *     // End with {@link #queue() queue()} to not block the callback thread!
 * 
* *

Example Planning:
*


 *     {@link net.dv8tion.jda.api.entities.channel.middleman.MessageChannel MessageChannel} channel = event.getChannel();
 *    {@literal RestAction} action = channel.sendMessage("This message will destroy itself in 5 seconds!");
 *     action.queue((message) {@literal ->} message.delete().{@link #queueAfter(long, TimeUnit) queueAfter(5, TimeUnit.SECONDS)});
 * 
* *

Developer Note: It is generally a good practice to use asynchronous logic because blocking threads requires resources * which can be avoided by using callbacks over blocking operations: *
{@link #queue(Consumer)} {@literal >} {@link #complete()} * *

There is a dedicated wiki page * for RestActions that can be useful for learning. * * @param * The generic response type for this RestAction * * @since 3.0 * * @see net.dv8tion.jda.api.exceptions.ErrorHandler * @see net.dv8tion.jda.api.exceptions.ErrorResponseException */ public interface RestAction { /** * If enabled this will pass a {@link net.dv8tion.jda.api.exceptions.ContextException ContextException} * as root-cause to all failure consumers. *
This might cause performance decrease due to the creation of exceptions for every execution. * *

It is recommended to pass a context consumer as failure manually using {@code queue(success, ContextException.here(failure))} * * @param enable * True, if context should be passed to all failure consumers */ static void setPassContext(boolean enable) { RestActionImpl.setPassContext(enable); } /** * Whether RestActions will use {@link net.dv8tion.jda.api.exceptions.ContextException ContextException} * automatically to keep track of the caller context. *
If set to {@code true} this can cause performance drops due to the creation of stack-traces on execution. * * @return True, if RestActions will keep track of context automatically * * @see #setPassContext(boolean) */ static boolean isPassContext() { return RestActionImpl.isPassContext(); } /** * The default failure callback used when none is provided in {@link #queue(Consumer, Consumer)}. * * @param callback * The fallback to use, or null to ignore failures (not recommended) */ static void setDefaultFailure(@Nullable final Consumer callback) { RestActionImpl.setDefaultFailure(callback); } /** * The default success callback used when none is provided in {@link #queue(Consumer, Consumer)} or {@link #queue(Consumer)}. * * @param callback * The fallback to use, or null to ignore success */ static void setDefaultSuccess(@Nullable final Consumer callback) { RestActionImpl.setDefaultSuccess(callback); } /** * Default timeout to apply to every RestAction. *
This will use no timeout unless specified otherwise. *
If the request doesn't get executed within the specified timeout it will fail. * *

When a RestAction times out, it will fail with a {@link java.util.concurrent.TimeoutException TimeoutException}. * * @param timeout * The default timeout to use * @param unit * {@link TimeUnit Unit} for the timeout value * * @throws IllegalArgumentException * If the provided unit is null */ static void setDefaultTimeout(long timeout, @Nonnull TimeUnit unit) { RestActionImpl.setDefaultTimeout(timeout, unit); } /** * The default timeout to apply to every RestAction in milliseconds. *
If no timeout has been configured, this will return 0. * *

When a RestAction times out, it will fail with a {@link java.util.concurrent.TimeoutException TimeoutException}. * * @return The default timeout in milliseconds, or 0 */ static long getDefaultTimeout() { return RestActionImpl.getDefaultTimeout(); } /** * The default failure callback used when none is provided in {@link #queue(Consumer, Consumer)}. * * @return The fallback consumer */ @Nonnull static Consumer getDefaultFailure() { return RestActionImpl.getDefaultFailure(); } /** * The default success callback used when none is provided in {@link #queue(Consumer, Consumer)} or {@link #queue(Consumer)}. * * @return The fallback consumer */ @Nonnull static Consumer getDefaultSuccess() { return RestActionImpl.getDefaultSuccess(); } /** * Creates a RestAction instance which accumulates all results of the provided actions. *
If one action fails, all others will be cancelled. * To handle failures individually instead of cancelling you can use {@link #mapToResult()}. * * @param first * The initial RestAction starting point * @param others * The remaining actions to accumulate * @param * The result type * * @throws IllegalArgumentException * If null is provided * * @return RestAction - Type: {@link List} of the results * * @see #and(RestAction, BiFunction) * @see #zip(RestAction, RestAction[]) * * @since 4.2.1 */ @Nonnull @SafeVarargs @CheckReturnValue static RestAction> allOf(@Nonnull RestAction first, @Nonnull RestAction... others) { Checks.notNull(first, "RestAction"); Checks.noneNull(others, "RestAction"); List> list = new ArrayList<>(others.length + 1); list.add(first); Collections.addAll(list, others); return allOf(list); } /** * Creates a RestAction instance which accumulates all results of the provided actions. *
If one action fails, all others will be cancelled. * To handle failures individually instead of cancelling you can use {@link #mapToResult()}. * * @param actions * Non-empty collection of RestActions to accumulate * @param * The result type * * @throws IllegalArgumentException * If null is provided or the collection is empty * * @return RestAction - Type: {@link List} of the results * * @see #and(RestAction, BiFunction) * @see #zip(RestAction, RestAction[]) * * @since 4.2.1 */ @Nonnull @CheckReturnValue static RestAction> allOf(@Nonnull Collection> actions) { return accumulate(actions, Collectors.toList()); } /** * Creates a RestAction instance which accumulates all results of the provided actions. *
If one action fails, all others will be cancelled. * To handle failures individually instead of cancelling you can use {@link #mapToResult()}. * * @param actions * Non-empty collection of RestActions to accumulate * @param collector * The {@link Collector} to use * @param * The input type * @param * The accumulator type * @param * The output type * * @throws IllegalArgumentException * If null is provided or the collection is empty * * @return RestAction - Type: {@link List} of the results * * @see #and(RestAction, BiFunction) * @see #zip(RestAction, RestAction[]) * * @since 4.2.1 */ @Nonnull @CheckReturnValue static RestAction accumulate(@Nonnull Collection> actions, @Nonnull Collector collector) { Checks.noneNull(actions, "RestAction"); Checks.notEmpty(actions, "RestActions"); Checks.notNull(collector, "Collector"); Supplier accumulator = collector.supplier(); BiConsumer add = collector.accumulator(); Function output = collector.finisher(); actions = new LinkedHashSet<>(actions); Iterator> iterator = actions.iterator(); RestAction result = iterator.next().map(it -> { A list = accumulator.get(); add.accept(list, it); return list; }); while (iterator.hasNext()) { RestAction next = iterator.next(); result = result.and(next, (list, b) -> { add.accept(list, b); return list; }); } return result.map(output); } /** * The current JDA instance * * @return The corresponding JDA instance */ @Nonnull JDA getJDA(); /** * Sets the last-second checks before finally executing the http request in the queue. *
If the provided supplier evaluates to {@code false} or throws an exception this will not be finished. * When an exception is thrown from the supplier it will be provided to the failure callback. * * @param checks * The checks to run before executing the request, or {@code null} to run no checks * * @return The current RestAction for chaining convenience * * @see #getCheck() * @see #addCheck(BooleanSupplier) */ @Nonnull RestAction setCheck(@Nullable BooleanSupplier checks); /** * The current checks for this RestAction. * * @return The current checks, or null if none were set * * @see #setCheck(BooleanSupplier) * * @since 4.2.1 */ @Nullable default BooleanSupplier getCheck() { return null; } /** * Shortcut for {@code setCheck(() -> getCheck().getAsBoolean() && checks.getAsBoolean())}. * * @param checks * Other checks to run * * @throws IllegalArgumentException * If the provided checks are null * * @return The current RestAction for chaining convenience * * @see #setCheck(BooleanSupplier) * * @since 4.2.1 */ @Nonnull @CheckReturnValue default RestAction addCheck(@Nonnull BooleanSupplier checks) { Checks.notNull(checks, "Checks"); BooleanSupplier check = getCheck(); return setCheck(() -> (check == null || check.getAsBoolean()) && checks.getAsBoolean()); } /** * Timeout for this RestAction instance. *
If the request doesn't get executed within the timeout it will fail. * *

When a RestAction times out, it will fail with a {@link java.util.concurrent.TimeoutException TimeoutException}. * This is the same as {@code deadline(System.currentTimeMillis() + unit.toMillis(timeout))}. * *

Example
*

{@code
     * action.timeout(10, TimeUnit.SECONDS) // 10 seconds from now
     *       .queueAfter(20, SECONDS); // request will not be executed within deadline and timeout immediately after 20 seconds
     * }
* * @param timeout * The timeout to use * @param unit * {@link TimeUnit Unit} for the timeout value * * @throws IllegalArgumentException * If the provided unit is null * * @return The same RestAction instance with the applied timeout * * @see #setDefaultTimeout(long, TimeUnit) */ @Nonnull default RestAction timeout(long timeout, @Nonnull TimeUnit unit) { Checks.notNull(unit, "TimeUnit"); return deadline(timeout <= 0 ? 0 : System.currentTimeMillis() + unit.toMillis(timeout)); } /** * Similar to {@link #timeout(long, TimeUnit)} but schedules a deadline at which the request has to be completed. *
If the deadline is reached, the request will fail with a {@link java.util.concurrent.TimeoutException TimeoutException}. * *

This does not mean that the request will immediately timeout when the deadline is reached. JDA will check the deadline * right before executing the request or within intervals in a worker thread. This only means the request will timeout * if the deadline has passed. * *

Example
*

{@code
     * action.deadline(System.currentTimeMillis() + 10000) // 10 seconds from now
     *       .queueAfter(20, SECONDS); // request will not be executed within deadline and timeout immediately after 20 seconds
     * }
* * @param timestamp * Millisecond timestamp at which the request will timeout * * @return The same RestAction with the applied deadline * * @see #timeout(long, TimeUnit) * @see #setDefaultTimeout(long, TimeUnit) */ @Nonnull default RestAction deadline(long timestamp) { throw new UnsupportedOperationException(); } /** * Submits a Request for execution. *
Using the default callback functions: * {@link #setDefaultSuccess(Consumer)} and {@link #setDefaultFailure(Consumer)} * *

To access the response you can use {@link #queue(java.util.function.Consumer)} * and to handle failures use {@link #queue(java.util.function.Consumer, java.util.function.Consumer)}. * *

This method is asynchronous * *

Example
*

{@code
     * public static void sendMessage(MessageChannel channel, String content)
     * {
     *     // sendMessage returns "MessageAction" which is a specialization for "RestAction"
     *     RestAction action = channel.sendMessage(content);
     *     // call queue() to send the message off to discord.
     *     action.queue();
     * }
     * }
* * @throws java.util.concurrent.RejectedExecutionException * If the requester has been shutdown by {@link JDA#shutdown()} or {@link JDA#shutdownNow()} * * @see net.dv8tion.jda.api.entities.channel.middleman.MessageChannel#sendMessage(java.lang.CharSequence) MessageChannel.sendMessage(CharSequence) * @see #queue(java.util.function.Consumer) queue(Consumer) * @see #queue(java.util.function.Consumer, java.util.function.Consumer) queue(Consumer, Consumer) */ default void queue() { queue(null); } /** * Submits a Request for execution. *
Using the default failure callback function. * *

To handle failures use {@link #queue(java.util.function.Consumer, java.util.function.Consumer)}. * *

This method is asynchronous * *

Example
*

{@code
     * public static void sendPrivateMessage(User user, String content)
     * {
     *     // The "" is the response type for the parameter in the success callback
     *     RestAction action = user.openPrivateChannel();
     *     // "channel" is the identifier we use to access the channel of the response
     *     // this is like the "user" we declared above, just a name for the function parameter
     *     action.queue((channel) -> channel.sendMessage(content).queue());
     * }
     * }
* * @throws java.util.concurrent.RejectedExecutionException * If the requester has been shutdown by {@link JDA#shutdown()} or {@link JDA#shutdownNow()} * * @param success * The success callback that will be called at a convenient time * for the API. (can be null) * * @see #queue(java.util.function.Consumer, java.util.function.Consumer) queue(Consumer, Consumer) */ default void queue(@Nullable Consumer success) { queue(success, null); } /** * Submits a Request for execution. * *

This method is asynchronous * *

Example
*

{@code
     * public static void sendPrivateMessage(JDA jda, String userId, String content)
     * {
     *     // Retrieve the user by their id
     *     RestAction action = jda.retrieveUserById(userId);
     *     action.queue(
     *         // Handle success if the user exists
     *         (user) -> user.openPrivateChannel().queue(
     *             (channel) -> channel.sendMessage(content).queue()),
     *
     *         // Handle failure if the user does not exist (or another issue appeared)
     *         (error) -> error.printStackTrace()
     *     );
     *
     *     // Alternatively use submit() to remove nested callbacks
     * }
     * }
* * @throws java.util.concurrent.RejectedExecutionException * If the requester has been shutdown by {@link JDA#shutdown()} or {@link JDA#shutdownNow()} * * @param success * The success callback that will be called at a convenient time * for the API. (can be null to use default) * @param failure * The failure callback that will be called if the Request * encounters an exception at its execution point. (can be null to use default) * * @see #submit() * @see net.dv8tion.jda.api.exceptions.ErrorHandler */ void queue(@Nullable Consumer success, @Nullable Consumer failure); /** * Blocks the current Thread and awaits the completion * of an {@link #submit()} request. *
Used for synchronous logic. * *

This might throw {@link java.lang.RuntimeException RuntimeExceptions} * * @throws java.util.concurrent.RejectedExecutionException * If the requester has been shutdown by {@link JDA#shutdown()} or {@link JDA#shutdownNow()} * @throws IllegalStateException * If used within a {@link #queue(Consumer, Consumer) queue(...)} callback * * @return The response value */ default T complete() { try { return complete(true); } catch (RateLimitedException e) { //This is so beyond impossible, but on the off chance that the laws of nature are rewritten // after the writing of this code, I'm placing this here. //Better safe than sorry? throw new AssertionError(e); } } /** * Blocks the current Thread and awaits the completion * of an {@link #submit()} request. *
Used for synchronous logic. * * @param shouldQueue * Whether this should automatically handle rate limitations (default true) * * @throws java.util.concurrent.RejectedExecutionException * If the requester has been shutdown by {@link JDA#shutdown()} or {@link JDA#shutdownNow()} * @throws IllegalStateException * If used within a {@link #queue(Consumer, Consumer) queue(...)} callback * @throws RateLimitedException * If we were rate limited and the {@code shouldQueue} is false. * Use {@link #complete()} to avoid this Exception. * * @return The response value */ T complete(boolean shouldQueue) throws RateLimitedException; /** * Submits a Request for execution and provides a {@link java.util.concurrent.CompletableFuture CompletableFuture} * representing its completion task. *
Cancelling the returned Future will result in the cancellation of the Request! * *

Example
*

{@code
     * public static void sendPrivateMessage(JDA jda, String userId, String content)
     * {
     *     // Retrieve the user by their id
     *     RestAction action = jda.retrieveUserById(userId);
     *     action.submit() // CompletableFuture
     *           // Handle success if the user exists
     *           .thenCompose((user) -> user.openPrivateChannel().submit()) // CompletableFuture
     *           .thenCompose((channel) -> channel.sendMessage(content).submit()) // CompletableFuture
     *           .whenComplete((v, error) -> {
     *               // Handle failure if the user does not exist (or another issue appeared)
     *               if (error != null) error.printStackTrace();
     *           });
     * }
     * }
* * @throws java.util.concurrent.RejectedExecutionException * If the requester has been shutdown by {@link JDA#shutdown()} or {@link JDA#shutdownNow()} * * @return Never-null {@link java.util.concurrent.CompletableFuture CompletableFuture} representing the completion promise */ @Nonnull default CompletableFuture submit() { return submit(true); } /** * Submits a Request for execution and provides a {@link java.util.concurrent.CompletableFuture CompletableFuture} * representing its completion task. *
Cancelling the returned Future will result in the cancellation of the Request! * * @throws java.util.concurrent.RejectedExecutionException * If the requester has been shutdown by {@link JDA#shutdown()} or {@link JDA#shutdownNow()} * * @param shouldQueue * Whether the Request should automatically handle rate limitations. (default true) * * @return Never-null {@link java.util.concurrent.CompletableFuture CompletableFuture} task representing the completion promise */ @Nonnull CompletableFuture submit(boolean shouldQueue); /** * Converts the success and failure callbacks into a {@link Result}. *
This means the {@link #queue(Consumer, Consumer)} failure consumer will never be used. * Instead, all results will be evaluated into a success consumer which provides an instance of {@link Result}. * *

{@link Result} will either be {@link Result#isSuccess() successful} or {@link Result#isFailure() failed}. * This can be useful in combination with {@link #allOf(Collection)} to handle failed requests individually for each * action. * *

Note: You have to handle failures explicitly with this. * You should use {@link Result#onFailure(Consumer)}, {@link Result#getFailure()}, or {@link Result#expect(Predicate)}! * * @return RestAction - Type: {@link Result} * * @since 4.2.1 */ @Nonnull @CheckReturnValue default RestAction> mapToResult() { return map(Result::success).onErrorMap(Result::failure); } /** * Intermediate operator that returns a modified RestAction. * *

This does not modify this instance but returns a new RestAction which will apply * the map function on successful execution. * *

Example
*

{@code
     * public RestAction retrieveMemberNickname(Guild guild, String userId) {
     *     return guild.retrieveMemberById(userId)
     *                 .map(Member::getNickname);
     * }
     * }
* * @param map * The mapping function to apply to the action result * * @param * The target output type * * @return RestAction for the mapped type * * @since 4.1.1 */ @Nonnull @CheckReturnValue default RestAction map(@Nonnull Function map) { Checks.notNull(map, "Function"); return new MapRestAction<>(this, map); } /** * An intermediate operator that returns a modified RestAction. * *

This does not modify this instance but returns a new RestAction, which will consume * the actions result using the given consumer on successful execution. * The resulting action continues with the previous result. * *

Example
*

{@code
     * public RestAction retrieveMemberNickname(Guild guild, String userId) {
     *     return guild.retrieveMemberById(userId)
     *                 .map(Member::getNickname)
     *                 .onSuccess(System.out::println);
     * }
     * }
* * Prefer using {@link #queue(Consumer)} instead, if continuation of the action * chain is not desired. * * @param consumer * The consuming function to apply to the action result, failures are propagated * into the resulting action * * @throws IllegalArgumentException * If the consumer is null * * @return RestAction that consumes the action result */ @Nonnull @CheckReturnValue default RestAction onSuccess(@Nonnull Consumer consumer) { Checks.notNull(consumer, "Consumer"); return map(result -> { consumer.accept(result); return result; }); } /** * Supply a fallback value when the RestAction fails for any reason. * *

This does not modify this instance but returns a new RestAction which will apply * the map function on failed execution. * *

Example
*

{@code
     * public RestAction sendMessage(User user, String content) {
     *     return user.openPrivateChannel() // RestAction
     *         .flatMap((channel) -> channel.sendMessage(content)) // RestAction
     *         .map(Message::getContentRaw) // RestAction
     *         .onErrorMap(Throwable::getMessage); // RestAction (must be the same as above)
     * }
     * }
* * @param map * The mapping function which provides the fallback value to use * * @throws IllegalArgumentException * If the mapping function is null * * @return RestAction with fallback handling * * @since 4.2.0 */ @Nonnull @CheckReturnValue default RestAction onErrorMap(@Nonnull Function map) { return onErrorMap(null, map); } /** * Supply a fallback value when the RestAction fails for a specific reason. * *

This does not modify this instance but returns a new RestAction which will apply * the map function on failed execution. * *

Example
*

{@code
     * public RestAction sendMessage(User user, String content) {
     *     return user.openPrivateChannel() // RestAction
     *         .flatMap((channel) -> channel.sendMessage(content)) // RestAction
     *         .map(Message::getContentRaw) // RestAction
     *         .onErrorMap(CANNOT_SEND_TO_USER::test, Throwable::getMessage); // RestAction (must be the same as above)
     * }
     * }
* * @param condition * A condition that must return true to apply this fallback * @param map * The mapping function which provides the fallback value to use * * @throws IllegalArgumentException * If the mapping function is null * * @return RestAction with fallback handling * * @see ErrorResponse#test(Throwable) * @see ErrorResponse#test(ErrorResponse...) * * @since 4.2.0 */ @Nonnull @CheckReturnValue default RestAction onErrorMap(@Nullable Predicate condition, @Nonnull Function map) { Checks.notNull(map, "Function"); return new MapErrorRestAction<>(this, condition == null ? (x) -> true : condition, map); } /** * Supply a fallback value when the RestAction fails for a any reason. * *

This does not modify this instance but returns a new RestAction which will apply * the map function on failed execution. * *

Example
*

{@code
     * public RestAction sendMessage(User user, TextChannel context, String content) {
     *     return user.openPrivateChannel() // RestAction
     *         .flatMap((channel) -> channel.sendMessage(content)) // RestAction
     *         .onErrorFlatMap(
     *             (error) -> context.sendMessage("Failed to send direct message to " + user.getAsMention() + " Reason: " + error)
     *         ); // RestAction (must be the same as above)
     * }
     * }
* * @param map * The mapping function which provides the fallback action to use * * @throws IllegalArgumentException * If the mapping function is null * * @return RestAction with fallback handling * * @since 4.2.0 */ @Nonnull @CheckReturnValue default RestAction onErrorFlatMap(@Nonnull Function> map) { return onErrorFlatMap(null, map); } /** * Supply a fallback value when the RestAction fails for a specific reason. * *

This does not modify this instance but returns a new RestAction which will apply * the map function on failed execution. * *

Example
*

{@code
     * public RestAction sendMessage(User user, TextChannel context, String content) {
     *     return user.openPrivateChannel() // RestAction
     *         .flatMap((channel) -> channel.sendMessage(content)) // RestAction
     *         .onErrorFlatMap(CANNOT_SEND_TO_USER::test,
     *             (error) -> context.sendMessage("Cannot send direct message to " + user.getAsMention())
     *         ); // RestAction (must be the same as above)
     * }
     * }
* * @param condition * A condition that must return true to apply this fallback * @param map * The mapping function which provides the fallback action to use * * @throws IllegalArgumentException * If the mapping function is null * * @return RestAction with fallback handling * * @see ErrorResponse#test(Throwable) * @see ErrorResponse#test(ErrorResponse...) * * @since 4.2.0 */ @Nonnull @CheckReturnValue default RestAction onErrorFlatMap(@Nullable Predicate condition, @Nonnull Function> map) { Checks.notNull(map, "Function"); return new FlatMapErrorRestAction<>(this, condition == null ? (x) -> true : condition, map); } /** * Intermediate operator that returns a modified RestAction. * *

This does not modify this instance but returns a new RestAction which will apply * the map function on successful execution. This will compute the result of both RestActions. *
The returned RestAction must not be null! * To terminate the execution chain on a specific condition you can use {@link #flatMap(Predicate, Function)}. * *

Example
*

{@code
     * public RestAction initializeGiveaway(Guild guild, String channelName) {
     *     return guild.createTextChannel(channelName)
     *          .addPermissionOverride(guild.getPublicRole(), null, EnumSet.of(Permission.MESSAGE_SEND)) // deny write for everyone
     *          .addPermissionOverride(guild.getSelfMember(), EnumSet.of(Permission.MESSAGE_SEND), null) // allow for self user
     *          .flatMap((channel) -> channel.sendMessage("React to enter giveaway!")) // send message
     *          .flatMap((message) -> message.addReaction(REACTION)); // add reaction
     * }
     * }
* * @param flatMap * The mapping function to apply to the action result, must return a RestAction * * @param * The target output type * * @return RestAction for the mapped type * * @since 4.1.1 */ @Nonnull @CheckReturnValue default RestAction flatMap(@Nonnull Function> flatMap) { return flatMap(null, flatMap); } /** * Intermediate operator that returns a modified RestAction. * *

This does not modify this instance but returns a new RestAction which will apply * the map function on successful execution. This will compute the result of both RestActions. *
The provided RestAction must not be null! * *

Example
*

{@code
     * private static final int MAX_COUNT = 1000;
     * public void updateCount(MessageChannel channel, String messageId, int count) {
     *     channel.retrieveMessageById(messageId) // retrieve message for check
     *         .map(Message::getContentRaw) // get content of the message
     *         .map(Integer::parseInt) // convert it to an int
     *         .flatMap(
     *             (currentCount) -> currentCount + count <= MAX_COUNT, // Only edit if new count does not exceed maximum
     *             (currentCount) -> channel.editMessageById(messageId, String.valueOf(currentCount + count)) // edit message
     *         )
     *         .map(Message::getContentRaw) // get content of the message
     *         .map(Integer::parseInt) // convert it to an int
     *         .queue((newCount) -> System.out.println("Updated count to " + newCount));
     * }
     * }
* * @param condition * A condition predicate that decides whether to apply the flat map operator or not * @param flatMap * The mapping function to apply to the action result, must return a RestAction * * @param * The target output type * * @return RestAction for the mapped type * * @see #flatMap(Function) * @see #map(Function) * * @since 4.1.1 */ @Nonnull @CheckReturnValue default RestAction flatMap(@Nullable Predicate condition, @Nonnull Function> flatMap) { Checks.notNull(flatMap, "Function"); return new FlatMapRestAction<>(this, condition, flatMap); } /** * Combines this RestAction with the provided action. *
The result is computed by the provided {@link BiFunction}. * *

If one of the actions fails, the other will be cancelled. * To handle failures individually instead of cancelling you can use {@link #mapToResult()}. * * @param other * The action to combine * @param accumulator * BiFunction to compute the result * @param * The type of the other action * @param * The result type after applying the accumulator function * * @throws IllegalArgumentException * If null is provided or you tried to combine an action with itself * * @return Combined RestAction * * @since 4.2.1 */ @Nonnull @CheckReturnValue default RestAction and(@Nonnull RestAction other, @Nonnull BiFunction accumulator) { Checks.notNull(other, "RestAction"); Checks.notNull(accumulator, "Accumulator"); return new CombineRestAction<>(this, other, accumulator); } /** * Combines this RestAction with the provided action. * *

If one of the actions fails, the other will be cancelled. * To handle failures individually instead of cancelling you can use {@link #mapToResult()}. * * @param other * The action to combine * @param * The type of the other action * * @throws IllegalArgumentException * If null is provided or you tried to combine an action with itself * * @return Combined RestAction with empty result * * @since 4.2.1 */ @Nonnull @CheckReturnValue default RestAction and(@Nonnull RestAction other) { return and(other, (a, b) -> null); } /** * Accumulates this RestAction with the provided actions into a {@link List}. * *

If one of the actions fails, the others will be cancelled. * To handle failures individually instead of cancelling you can use {@link #mapToResult()}. * * @param first * The first other action to accumulate into the list * @param other * The other actions to accumulate into the list * * @throws IllegalArgumentException * If null is provided or you tried to combine an action with itself * * @return Combined RestAction with empty result * * @see #allOf(RestAction, RestAction[]) * @see #and(RestAction, BiFunction) * * @since 4.2.1 */ @Nonnull @CheckReturnValue @SuppressWarnings("unchecked") default RestAction> zip(@Nonnull RestAction first, @Nonnull RestAction... other) { Checks.notNull(first, "RestAction"); Checks.noneNull(other, "RestAction"); List> list = new ArrayList<>(); list.add(this); list.add(first); Collections.addAll(list, other); return allOf(list); } /** * Intermediate operator that returns a modified RestAction. * *

This does not modify this instance but returns a new RestAction which will delay its result by the provided delay. * *

Example
*

{@code
     * public RestAction selfDestruct(MessageChannel channel, String content) {
     *     return channel.sendMessage("The following message will destroy itself in 1 minute!")
     *         .delay(Duration.ofSeconds(10)) // edit 10 seconds later
     *         .flatMap((it) -> it.editMessage(content))
     *         .delay(Duration.ofMinutes(1)) // delete 1 minute later
     *         .flatMap(Message::delete);
     * }
     * }
* * @param duration * The delay * * @return RestAction with delay * * @see #queueAfter(long, TimeUnit) * * @since 4.1.1 */ @Nonnull @CheckReturnValue default RestAction delay(@Nonnull Duration duration) { return delay(duration, null); } /** * Intermediate operator that returns a modified RestAction. * *

This does not modify this instance but returns a new RestAction which will delay its result by the provided delay. * *

Example
*

{@code
     * public RestAction selfDestruct(MessageChannel channel, String content) {
     *     return channel.sendMessage("The following message will destroy itself in 1 minute!")
     *         .delay(Duration.ofSeconds(10), scheduler) // edit 10 seconds later
     *         .flatMap((it) -> it.editMessage(content))
     *         .delay(Duration.ofMinutes(1), scheduler) // delete 1 minute later
     *         .flatMap(Message::delete);
     * }
     * }
* * @param duration * The delay * @param scheduler * The scheduler to use, null to use {@link JDA#getRateLimitPool()} * * @return RestAction with delay * * @see #queueAfter(long, TimeUnit, ScheduledExecutorService) * * @since 4.1.1 */ @Nonnull @CheckReturnValue default RestAction delay(@Nonnull Duration duration, @Nullable ScheduledExecutorService scheduler) { Checks.notNull(duration, "Duration"); return new DelayRestAction<>(this, TimeUnit.MILLISECONDS, duration.toMillis(), scheduler); } /** * Intermediate operator that returns a modified RestAction. * *

This does not modify this instance but returns a new RestAction which will delay its result by the provided delay. * *

Example
*

{@code
     * public RestAction selfDestruct(MessageChannel channel, String content) {
     *     return channel.sendMessage("The following message will destroy itself in 1 minute!")
     *         .delay(10, SECONDS) // edit 10 seconds later
     *         .flatMap((it) -> it.editMessage(content))
     *         .delay(1, MINUTES) // delete 1 minute later
     *         .flatMap(Message::delete);
     * }
     * }
* * @param delay * The delay value * @param unit * The time unit for the delay value * * @return RestAction with delay * * @see #queueAfter(long, TimeUnit) * * @since 4.1.1 */ @Nonnull @CheckReturnValue default RestAction delay(long delay, @Nonnull TimeUnit unit) { return delay(delay, unit, null); } /** * Intermediate operator that returns a modified RestAction. * *

This does not modify this instance but returns a new RestAction which will delay its result by the provided delay. * *

Example
*

{@code
     * public RestAction selfDestruct(MessageChannel channel, String content) {
     *     return channel.sendMessage("The following message will destroy itself in 1 minute!")
     *         .delay(10, SECONDS, scheduler) // edit 10 seconds later
     *         .flatMap((it) -> it.editMessage(content))
     *         .delay(1, MINUTES, scheduler) // delete 1 minute later
     *         .flatMap(Message::delete);
     * }
     * }
* * @param delay * The delay value * @param unit * The time unit for the delay value * @param scheduler * The scheduler to use, null to use {@link JDA#getRateLimitPool()} * * @return RestAction with delay * * @see #queueAfter(long, TimeUnit, ScheduledExecutorService) * * @since 4.1.1 */ @Nonnull @CheckReturnValue default RestAction delay(long delay, @Nonnull TimeUnit unit, @Nullable ScheduledExecutorService scheduler) { Checks.notNull(unit, "TimeUnit"); return new DelayRestAction<>(this, unit, delay, scheduler); } /** * Schedules a call to {@link #queue()} to be executed after the specified {@code delay}. *
This is an asynchronous operation that will return a * {@link CompletableFuture CompletableFuture} representing the task. * *

Similar to {@link #queueAfter(long, TimeUnit)} but does not require callbacks to be passed. * Continuations of {@link CompletableFuture} can be used instead. * *

The global JDA RateLimit {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} * is used for this operation. *
You can provide your own Executor using {@link #submitAfter(long, java.util.concurrent.TimeUnit, java.util.concurrent.ScheduledExecutorService)}! * * @param delay * The delay after which this computation should be executed, negative to execute immediately * @param unit * The {@link java.util.concurrent.TimeUnit TimeUnit} to convert the specified {@code delay} * * @throws java.lang.IllegalArgumentException * If the provided TimeUnit is {@code null} * * @return {@link DelayedCompletableFuture DelayedCompletableFuture} * representing the delayed operation */ @Nonnull default DelayedCompletableFuture submitAfter(long delay, @Nonnull TimeUnit unit) { return submitAfter(delay, unit, null); } /** * Schedules a call to {@link #queue()} to be executed after the specified {@code delay}. *
This is an asynchronous operation that will return a * {@link CompletableFuture CompletableFuture} representing the task. * *

Similar to {@link #queueAfter(long, TimeUnit)} but does not require callbacks to be passed. * Continuations of {@link CompletableFuture} can be used instead. * *

The specified {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} is used for this operation. * * @param delay * The delay after which this computation should be executed, negative to execute immediately * @param unit * The {@link java.util.concurrent.TimeUnit TimeUnit} to convert the specified {@code delay} * @param executor * The {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} that should be used * to schedule this operation, or null to use the default * * @throws java.lang.IllegalArgumentException * If the provided TimeUnit is {@code null} * * @return {@link DelayedCompletableFuture DelayedCompletableFuture} * representing the delayed operation */ @Nonnull default DelayedCompletableFuture submitAfter(long delay, @Nonnull TimeUnit unit, @Nullable ScheduledExecutorService executor) { Checks.notNull(unit, "TimeUnit"); if (executor == null) executor = getJDA().getRateLimitPool(); return DelayedCompletableFuture.make(executor, delay, unit, (task) -> { final Consumer onFailure; if (isPassContext()) onFailure = ContextException.here(task::completeExceptionally); else onFailure = task::completeExceptionally; return new ContextRunnable(() -> queue(task::complete, onFailure)); }); } /** * Blocks the current Thread for the specified delay and calls {@link #complete()} * when delay has been reached. *
If the specified delay is negative this action will execute immediately. (see: {@link TimeUnit#sleep(long)}) * * @param delay * The delay after which to execute a call to {@link #complete()} * @param unit * The {@link java.util.concurrent.TimeUnit TimeUnit} which should be used * (this will use {@link java.util.concurrent.TimeUnit#sleep(long) unit.sleep(delay)}) * * @throws java.lang.IllegalArgumentException * If the specified {@link java.util.concurrent.TimeUnit TimeUnit} is {@code null} * @throws java.lang.RuntimeException * If the sleep operation is interrupted * * @return The response value */ default T completeAfter(long delay, @Nonnull TimeUnit unit) { Checks.notNull(unit, "TimeUnit"); try { unit.sleep(delay); return complete(); } catch (InterruptedException e) { throw new RuntimeException(e); } } /** * Schedules a call to {@link #queue()} to be executed after the specified {@code delay}. *
This is an asynchronous operation that will return a * {@link java.util.concurrent.ScheduledFuture ScheduledFuture} representing the task. * *

This operation gives no access to the response value. *
Use {@link #queueAfter(long, java.util.concurrent.TimeUnit, java.util.function.Consumer)} to access * the success consumer for {@link #queue(java.util.function.Consumer)}! * *

The global JDA {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} is used for this operation. *
You can provide your own Executor with {@link #queueAfter(long, java.util.concurrent.TimeUnit, java.util.concurrent.ScheduledExecutorService)} * * @param delay * The delay after which this computation should be executed, negative to execute immediately * @param unit * The {@link java.util.concurrent.TimeUnit TimeUnit} to convert the specified {@code delay} * * @throws java.lang.IllegalArgumentException * If the provided TimeUnit is {@code null} * * @return {@link java.util.concurrent.ScheduledFuture ScheduledFuture} * representing the delayed operation */ @Nonnull default ScheduledFuture queueAfter(long delay, @Nonnull TimeUnit unit) { return queueAfter(delay, unit, null, null, null); } /** * Schedules a call to {@link #queue(java.util.function.Consumer)} to be executed after the specified {@code delay}. *
This is an asynchronous operation that will return a * {@link java.util.concurrent.ScheduledFuture ScheduledFuture} representing the task. * *

This operation gives no access to the failure callback. *
Use {@link #queueAfter(long, java.util.concurrent.TimeUnit, java.util.function.Consumer, java.util.function.Consumer)} to access * the failure consumer for {@link #queue(java.util.function.Consumer, java.util.function.Consumer)}! * *

The global JDA {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} is used for this operation. *
You can provide your own Executor with {@link #queueAfter(long, java.util.concurrent.TimeUnit, java.util.function.Consumer, java.util.concurrent.ScheduledExecutorService)} * * @param delay * The delay after which this computation should be executed, negative to execute immediately * @param unit * The {@link java.util.concurrent.TimeUnit TimeUnit} to convert the specified {@code delay} * @param success * The success {@link java.util.function.Consumer Consumer} that should be called * once the {@link #queue(java.util.function.Consumer)} operation completes successfully. * * @throws java.lang.IllegalArgumentException * If the provided TimeUnit is {@code null} * * @return {@link java.util.concurrent.ScheduledFuture ScheduledFuture} * representing the delayed operation */ @Nonnull default ScheduledFuture queueAfter(long delay, @Nonnull TimeUnit unit, @Nullable Consumer success) { return queueAfter(delay, unit, success, null, null); } /** * Schedules a call to {@link #queue(java.util.function.Consumer, java.util.function.Consumer)} * to be executed after the specified {@code delay}. *
This is an asynchronous operation that will return a * {@link java.util.concurrent.ScheduledFuture ScheduledFuture} representing the task. * *

The global JDA {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} is used for this operation. *
You provide your own Executor with {@link #queueAfter(long, java.util.concurrent.TimeUnit, java.util.function.Consumer, java.util.function.Consumer, java.util.concurrent.ScheduledExecutorService)} * * @param delay * The delay after which this computation should be executed, negative to execute immediately * @param unit * The {@link java.util.concurrent.TimeUnit TimeUnit} to convert the specified {@code delay} * @param success * The success {@link java.util.function.Consumer Consumer} that should be called * once the {@link #queue(java.util.function.Consumer, java.util.function.Consumer)} operation completes successfully. * @param failure * The failure {@link java.util.function.Consumer Consumer} that should be called * in case of an error of the {@link #queue(java.util.function.Consumer, java.util.function.Consumer)} operation. * * @throws java.lang.IllegalArgumentException * If the provided TimeUnit is {@code null} * * @return {@link java.util.concurrent.ScheduledFuture ScheduledFuture} * representing the delayed operation * * @see net.dv8tion.jda.api.exceptions.ErrorHandler */ @Nonnull default ScheduledFuture queueAfter(long delay, @Nonnull TimeUnit unit, @Nullable Consumer success, @Nullable Consumer failure) { return queueAfter(delay, unit, success, failure, null); } /** * Schedules a call to {@link #queue()} to be executed after the specified {@code delay}. *
This is an asynchronous operation that will return a * {@link java.util.concurrent.ScheduledFuture ScheduledFuture} representing the task. * *

This operation gives no access to the response value. *
Use {@link #queueAfter(long, java.util.concurrent.TimeUnit, java.util.function.Consumer)} to access * the success consumer for {@link #queue(java.util.function.Consumer)}! * *

The specified {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} is used for this operation. * * @param delay * The delay after which this computation should be executed, negative to execute immediately * @param unit * The {@link java.util.concurrent.TimeUnit TimeUnit} to convert the specified {@code delay} * @param executor * The Non-null {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} that should be used * to schedule this operation * * @throws java.lang.IllegalArgumentException * If the provided TimeUnit or ScheduledExecutorService is {@code null} * * @return {@link java.util.concurrent.ScheduledFuture ScheduledFuture} * representing the delayed operation */ @Nonnull default ScheduledFuture queueAfter(long delay, @Nonnull TimeUnit unit, @Nullable ScheduledExecutorService executor) { return queueAfter(delay, unit, null, null, executor); } /** * Schedules a call to {@link #queue(java.util.function.Consumer)} to be executed after the specified {@code delay}. *
This is an asynchronous operation that will return a * {@link java.util.concurrent.ScheduledFuture ScheduledFuture} representing the task. * *

This operation gives no access to the failure callback. *
Use {@link #queueAfter(long, java.util.concurrent.TimeUnit, java.util.function.Consumer, java.util.function.Consumer)} to access * the failure consumer for {@link #queue(java.util.function.Consumer, java.util.function.Consumer)}! * *

The specified {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} is used for this operation. * * @param delay * The delay after which this computation should be executed, negative to execute immediately * @param unit * The {@link java.util.concurrent.TimeUnit TimeUnit} to convert the specified {@code delay} * @param success * The success {@link java.util.function.Consumer Consumer} that should be called * once the {@link #queue(java.util.function.Consumer)} operation completes successfully. * @param executor * The Non-null {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} that should be used * to schedule this operation * * @throws java.lang.IllegalArgumentException * If the provided TimeUnit or ScheduledExecutorService is {@code null} * * @return {@link java.util.concurrent.ScheduledFuture ScheduledFuture} * representing the delayed operation */ @Nonnull default ScheduledFuture queueAfter(long delay, @Nonnull TimeUnit unit, @Nullable Consumer success, @Nullable ScheduledExecutorService executor) { return queueAfter(delay, unit, success, null, executor); } /** * Schedules a call to {@link #queue(java.util.function.Consumer, java.util.function.Consumer)} * to be executed after the specified {@code delay}. *
This is an asynchronous operation that will return a * {@link java.util.concurrent.ScheduledFuture ScheduledFuture} representing the task. * *

The specified {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} is used for this operation. * * @param delay * The delay after which this computation should be executed, negative to execute immediately * @param unit * The {@link java.util.concurrent.TimeUnit TimeUnit} to convert the specified {@code delay} * @param success * The success {@link java.util.function.Consumer Consumer} that should be called * once the {@link #queue(java.util.function.Consumer, java.util.function.Consumer)} operation completes successfully. * @param failure * The failure {@link java.util.function.Consumer Consumer} that should be called * in case of an error of the {@link #queue(java.util.function.Consumer, java.util.function.Consumer)} operation. * @param executor * The Non-null {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} that should be used * to schedule this operation * * @throws java.lang.IllegalArgumentException * If the provided TimeUnit or ScheduledExecutorService is {@code null} * * @return {@link java.util.concurrent.ScheduledFuture ScheduledFuture} * representing the delayed operation * * @see net.dv8tion.jda.api.exceptions.ErrorHandler */ @Nonnull default ScheduledFuture queueAfter(long delay, @Nonnull TimeUnit unit, @Nullable Consumer success, @Nullable Consumer failure, @Nullable ScheduledExecutorService executor) { Checks.notNull(unit, "TimeUnit"); if (executor == null) executor = getJDA().getRateLimitPool(); final Consumer onFailure; if (isPassContext()) onFailure = ContextException.here(failure == null ? getDefaultFailure() : failure); else onFailure = failure; Runnable task = new ContextRunnable(() -> queue(success, onFailure)); return executor.schedule(task, delay, unit); } }