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

io.activej.promise.Promises Maven / Gradle / Ivy

Go to download

A convenient way to organize asynchronous code. Promises are a faster and more efficient version of JavaScript's Promise and Java's CompletionStage's.

There is a newer version: 6.0-rc2
Show newest version
/*
 * Copyright (C) 2020 ActiveJ LLC.
 *
 * 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 io.activej.promise;

import io.activej.async.AsyncAccumulator;
import io.activej.async.AsyncBuffer;
import io.activej.async.exception.AsyncTimeoutException;
import io.activej.async.function.AsyncFunction;
import io.activej.async.function.AsyncRunnable;
import io.activej.async.function.AsyncSupplier;
import io.activej.common.annotation.StaticFactories;
import io.activej.common.function.BiConsumerEx;
import io.activej.common.function.FunctionEx;
import io.activej.common.recycle.Recyclers;
import io.activej.common.tuple.*;
import io.activej.reactor.Reactor;
import io.activej.reactor.schedule.ReactorScheduler;
import io.activej.reactor.schedule.ScheduledRunnable;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Array;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Stream;

import static io.activej.common.Utils.iteratorOf;
import static io.activej.common.Utils.transformIterator;
import static io.activej.common.exception.FatalErrorHandler.handleError;
import static io.activej.promise.PromisePredicates.isResult;
import static io.activej.reactor.Reactor.getCurrentReactor;
import static java.util.Arrays.asList;

/**
 * Allows managing multiple {@link Promise}s.
 */
@SuppressWarnings({"WeakerAccess", "unchecked", "RedundantCast"})
@StaticFactories(Promise.class)
public class Promises {
	/**
	 * @see #timeout(long, Promise)
	 */
	@Contract(pure = true)
	public static  Promise timeout(Duration delay, Promise promise) {
		return timeout((ReactorScheduler) getCurrentReactor(), delay, promise);
	}

	public static  Promise timeout(ReactorScheduler reactor, Duration delay, Promise promise) {
		return timeout(reactor, delay.toMillis(), promise);
	}

	/**
	 * Waits until the delay passes and if the {@code Promise} is still
	 * not complete, tries to complete it with {@code TIMEOUT_EXCEPTION}.
	 *
	 * @param delay   time of delay
	 * @param promise the Promise to be tracked
	 * @return {@code Promise}
	 */
	@Contract(pure = true)
	public static  Promise timeout(long delay, Promise promise) {
		return timeout((ReactorScheduler) getCurrentReactor(), delay, promise);
	}

	public static  Promise timeout(ReactorScheduler reactor, long delay, Promise promise) {
		if (promise.isComplete()) return promise;
		if (delay <= 0) return Promise.ofException(new AsyncTimeoutException("Promise timeout"));
		return Promise.ofCallback(cb -> {
			ScheduledRunnable scheduled = new ScheduledRunnable(reactor.currentTimeMillis() + delay) {
				@Override
				public void run() {
					cb.trySetException(new AsyncTimeoutException("Promise timeout"));
				}
			};
			reactor.schedule(scheduled);
			promise.subscribe((result, e) -> {
				scheduled.cancel();
				if (!cb.trySet(result, e)) {
					Recyclers.recycle(result);
				}
			});
		});
	}

	@Contract(pure = true)
	public static Promise delay(Duration delay) {
		return delay((ReactorScheduler) getCurrentReactor(), delay);
	}

	public static Promise delay(ReactorScheduler reactor, Duration delay) {
		return delay(reactor, delay.toMillis());
	}

	@Contract(pure = true)
	public static Promise delay(long delayMillis) {
		return delay((ReactorScheduler) getCurrentReactor(), delayMillis);
	}

	public static Promise delay(ReactorScheduler reactor, long delayMillis) {
		if (delayMillis <= 0) return Promise.of(null);
		return Promise.ofCallback(cb ->
			reactor.schedule(new ScheduledRunnable(reactor.currentTimeMillis() + delayMillis) {
				@Override
				public void run() {
					cb.set(null);
				}
			}));
	}

	@Contract(pure = true)
	public static  Promise delay(Duration delay, T value) {
		return delay((ReactorScheduler) getCurrentReactor(), delay, value);
	}

	public static  Promise delay(ReactorScheduler reactor, Duration delay, T value) {
		return delay(reactor, delay.toMillis(), value);
	}

	@Contract(pure = true)
	public static  Promise delay(long delayMillis, T value) {
		return delay((ReactorScheduler) getCurrentReactor(), delayMillis, value);
	}

	public static  Promise delay(ReactorScheduler reactor, long delayMillis, T value) {
		if (delayMillis <= 0) return Promise.of(value);
		return Promise.ofCallback(cb ->
			reactor.schedule(new ScheduledRunnable(reactor.currentTimeMillis() + delayMillis) {
				@Override
				public void run() {
					cb.set(value);
				}
			}));
	}

	/**
	 * @see #delay(long, Promise)
	 */
	@Contract(pure = true)
	public static  Promise delay(Duration delay, Promise promise) {
		return delay((ReactorScheduler) getCurrentReactor(), delay, promise);
	}

	public static  Promise delay(ReactorScheduler reactor, Duration delay, Promise promise) {
		return delay(reactor, delay.toMillis(), promise);
	}

	/**
	 * Delays completion of provided {@code promise} for
	 * the defined period of time.
	 *
	 * @param delayMillis delay in millis
	 * @param promise     the {@code Promise} to be delayed
	 * @return completed {@code Promise}
	 */
	@Contract(pure = true)
	public static  Promise delay(long delayMillis, Promise promise) {
		return delay((ReactorScheduler) getCurrentReactor(), delayMillis, promise);
	}

	public static  Promise delay(ReactorScheduler reactor, long delayMillis, Promise promise) {
		if (delayMillis <= 0) return promise;
		return Promise.ofCallback(cb ->
			reactor.schedule(new ScheduledRunnable(reactor.currentTimeMillis() + delayMillis) {
				@Override
				public void run() {
					promise.subscribe(cb);
				}
			}));
	}

	@Contract(pure = true)
	public static  Promise interval(Duration interval, Promise promise) {
		return interval((ReactorScheduler) getCurrentReactor(), interval, promise);
	}

	public static  Promise interval(ReactorScheduler reactor, Duration interval, Promise promise) {
		return interval(reactor, interval.toMillis(), promise);
	}

	@Contract(pure = true)
	public static  Promise interval(long intervalMillis, Promise promise) {
		return interval((ReactorScheduler) getCurrentReactor(), intervalMillis, promise);
	}

	public static  Promise interval(ReactorScheduler reactor, long intervalMillis, Promise promise) {
		return intervalMillis <= 0 ?
			promise :
			promise.then(value -> delay(reactor, intervalMillis, value));
	}

	/**
	 * @see #schedule(long, Promise)
	 */
	@Contract(pure = true)
	public static Promise schedule(Instant instant) {
		return schedule((ReactorScheduler) getCurrentReactor(), instant);
	}

	public static Promise schedule(ReactorScheduler reactor, Instant instant) {
		return schedule(reactor, instant.toEpochMilli());
	}

	/**
	 * @see #schedule(long, Promise)
	 */
	@Contract(pure = true)
	public static Promise schedule(long timestamp) {
		return schedule((ReactorScheduler) getCurrentReactor(), timestamp);
	}

	public static Promise schedule(ReactorScheduler reactor, long timestamp) {
		return Promise.ofCallback(cb ->
			reactor.schedule(new ScheduledRunnable(timestamp) {
				@Override
				public void run() {
					cb.set(null);
				}
			}));
	}

	/**
	 * @see #schedule(long, Promise)
	 */
	@Contract(pure = true)
	public static  Promise schedule(Instant instant, T value) {
		return schedule((ReactorScheduler) getCurrentReactor(), instant.toEpochMilli(), value);
	}

	public static  Promise schedule(ReactorScheduler reactor, Instant instant, T value) {
		return schedule(reactor, instant.toEpochMilli(), value);
	}

	/**
	 * @see #schedule(long, Promise)
	 */
	@Contract(pure = true)
	public static  Promise schedule(long timestamp, T value) {
		return schedule((ReactorScheduler) getCurrentReactor(), timestamp, value);
	}

	public static  Promise schedule(ReactorScheduler reactor, long timestamp, T value) {
		return Promise.ofCallback(cb ->
			reactor.schedule(new ScheduledRunnable(timestamp) {
				@Override
				public void run() {
					cb.set(value);
				}
			}));
	}

	/**
	 * @see #schedule(long, Promise)
	 */
	@Contract(pure = true)
	public static  Promise schedule(Instant instant, Promise promise) {
		return schedule((ReactorScheduler) getCurrentReactor(), instant, promise);
	}

	public static  Promise schedule(ReactorScheduler reactor, Instant instant, Promise promise) {
		return schedule(reactor, instant.toEpochMilli(), promise);
	}

	/**
	 * Schedules completion of the {@code Promise} so that it will
	 * be completed after the timestamp even if its operations
	 * were completed earlier.
	 */
	@Contract(pure = true)
	public static  Promise schedule(long timestamp, Promise promise) {
		return schedule((ReactorScheduler) getCurrentReactor(), timestamp, promise);
	}

	public static  Promise schedule(ReactorScheduler reactor, long timestamp, Promise promise) {
		return Promise.ofCallback(cb -> reactor.schedule(new ScheduledRunnable(timestamp) {
			@Override
			public void run() {
				promise.subscribe(cb);
			}
		}));
	}

	/**
	 * @see Promises#all(List)
	 */
	@Contract(pure = true)
	public static Promise all() {
		return Promise.complete();
	}

	/**
	 * @see Promises#all(List)
	 */
	@Contract(pure = true)
	public static Promise all(Promise promise1) {
		return promise1.toVoid();
	}

	/**
	 * Optimized for 2 promises.
	 *
	 * @see Promises#all(List)
	 */
	@Contract(pure = true)
	public static Promise all(Promise promise1, Promise promise2) {
		return promise1.both(promise2);
	}

	/**
	 * @see Promises#all(List)
	 */
	@Contract(pure = true)
	public static Promise all(Promise... promises) {
		return all(List.of(promises));
	}

	/**
	 * Returns a {@code Promise} that completes when
	 * all of the {@code promises} are completed.
	 */
	@Contract(pure = true)
	public static Promise all(List> promises) {
		int size = promises.size();
		if (size == 0) return Promise.complete();
		if (size == 1) return promises.get(0).map(AbstractPromise::recycleToVoid);
		if (size == 2) return promises.get(0).both(promises.get(1));

		return allIterator(promises.iterator(), true);
	}

	/**
	 * @see Promises#all(List)
	 */
	@Contract(pure = true)
	public static Promise all(Stream> promises) {
		return all(promises.iterator());
	}

	/**
	 * Returns {@code Promise} that completes when all of the {@code promises}
	 * are completed. If at least one of the {@code promises} completes
	 * exceptionally, a {@link CompleteExceptionallyPromise} will be returned.
	 */
	public static Promise all(Iterator> promises) {
		return allIterator(promises, false);
	}

	private static Promise allIterator(Iterator> promises, boolean ownership) {
		if (!promises.hasNext()) return all();
		PromiseAll resultPromise = new PromiseAll<>();
		while (promises.hasNext()) {
			Promise promise = promises.next();
			if (promise.isResult()) {
				Recyclers.recycle(promise.getResult());
				continue;
			}
			if (promise.isException()) {
				if (ownership) {
					promises.forEachRemaining(Recyclers::recycle);
				} else {
					Recyclers.recycle(promises);
				}
				return Promise.ofException(promise.getException());
			}
			resultPromise.countdown++;
			promise.next(resultPromise);
		}
		resultPromise.countdown--;
		return resultPromise.countdown == 0 ? Promise.complete() : resultPromise;
	}

	/**
	 * Returns a {@link CompleteExceptionallyPromise} with {@link Exception},
	 * since this method doesn't accept any {@code Promise}s
	 *
	 * @see #any(Iterator)
	 */
	@Contract(pure = true)
	public static  Promise any() {
		return Promise.ofException(new Exception("There are no promises to be complete"));
	}

	/**
	 * @see #any(Iterator)
	 */
	@Contract(pure = true)
	public static  Promise any(Promise promise1) {
		return (Promise) promise1;
	}

	/**
	 * Optimized for 2 promises.
	 *
	 * @see #any(Iterator)
	 */
	@Contract(pure = true)
	public static  Promise any(Promise promise1, Promise promise2) {
		return ((Promise) promise1).either(promise2);
	}

	/**
	 * @see #any(Iterator)
	 */
	@Contract(pure = true)
	@SafeVarargs
	public static  Promise any(Promise... promises) {
		return any(isResult(), List.of(promises));
	}

	/**
	 * @see #any(Iterator)
	 */
	@Contract(pure = true)
	public static  Promise any(List> promises) {
		int size = promises.size();
		if (size == 0) return any();
		if (size == 1) return (Promise) promises.get(0);
		if (size == 2) return ((Promise) promises.get(0)).either(promises.get(1));
		return any(isResult(), promises);
	}

	/**
	 * @see #any(Iterator)
	 */
	@Contract(pure = true)
	public static  Promise any(Stream> promises) {
		return any(isResult(), promises.iterator());
	}

	public static  Promise any(Iterator> promises) {
		return any(isResult(), promises);
	}

	@Contract(pure = true)
	public static  Promise any(BiPredicate predicate, Promise promise1) {
		return any(predicate, List.of(promise1));
	}

	@Contract(pure = true)
	public static  Promise any(BiPredicate predicate, Promise promise1, Promise promise2) {
		return any(predicate, List.of(promise1, promise2));
	}

	@Contract(pure = true)
	@SafeVarargs
	public static  Promise any(BiPredicate predicate, Promise... promises) {
		return any(predicate, List.of(promises));
	}

	@Contract(pure = true)
	public static  Promise any(BiPredicate predicate, Stream> promises) {
		return any(predicate, promises.iterator());
	}

	@Contract(pure = true)
	public static  Promise any(BiPredicate predicate, List> promises) {
		return anyIterator(predicate, promises.iterator(), true);
	}

	public static  Promise any(BiPredicate predicate, Iterator> promises) {
		return anyIterator(predicate, promises, false);
	}

	private static  Promise anyIterator(
		BiPredicate predicate, Iterator> promises,
		boolean ownership
	) {
		if (!promises.hasNext()) return any();
		PromiseAny resultPromise = new PromiseAny<>(predicate);
		while (promises.hasNext()) {
			Promise promise = promises.next();
			if (promise.isComplete()) {
				T result = promise.getResult();
				if (predicate.test(result, promise.getException())) {
					if (ownership) {
						promises.forEachRemaining(Recyclers::recycle);
					} else {
						Recyclers.recycle(promises);
					}
					return Promise.of(result);
				}
				Recyclers.recycle(result);
				continue;
			}
			resultPromise.countdown++;
			promise.next(resultPromise);
		}
		resultPromise.countdown--;
		return resultPromise.countdown == 0 ? any() : resultPromise;
	}

	/**
	 * Returns a successfully completed {@code Promise}
	 * with an empty list as the result.
	 */
	@Contract(pure = true)
	public static  Promise> toList() {
		return Promise.of(List.of());
	}

	/**
	 * Returns a completed {@code Promise}
	 * with a result wrapped in {@code List}.
	 */
	@Contract(pure = true)
	public static  Promise> toList(Promise promise1) {
		return promise1.map(t -> List.of(t));
	}

	/**
	 * Returns {@code Promise} with a list of {@code promise1} and {@code promise2} results.
	 */
	@Contract(pure = true)
	public static  Promise> toList(Promise promise1, Promise promise2) {
		return promise1.combine(promise2, List::of);
	}

	/**
	 * @see Promises#toList(List)
	 */
	@Contract(pure = true)
	@SafeVarargs
	public static  Promise> toList(Promise... promises) {
		return toList(List.of(promises));
	}

	/**
	 * Reduces list of {@code Promise}s into Promise<List>.
	 */
	@Contract(pure = true)
	public static  Promise> toList(List> promises) {
		int size = promises.size();
		if (size == 0) return Promise.of(List.of());
		if (size == 1) return promises.get(0).map(t -> List.of(t));
		if (size == 2) return promises.get(0).combine(promises.get(1), List::of);
		return toListImpl(promises.iterator(), promises.size(), true);
	}

	/**
	 * @see Promises#toList(List)
	 */
	@Contract(pure = true)
	public static  Promise> toList(Stream> promises) {
		return toList(promises.iterator());
	}

	/**
	 * @see Promises#toList(List)
	 */
	@Contract(pure = true)
	public static  Promise> toList(Iterable> promises) {
		return toList(promises.iterator());
	}

	/**
	 * @see Promises#toList(List)
	 */
	@Contract(pure = true)
	public static  Promise> toList(Iterator> promises) {
		return toListImpl(promises, 10, false);
	}

	/**
	 * @see Promises#toList(List)
	 */
	@Contract(pure = true)
	private static  Promise> toListImpl(Iterator> promises, int initialSize, boolean ownership) {
		PromisesToList resultPromise = new PromisesToList<>(initialSize);

		for (int i = 0; promises.hasNext() && !resultPromise.isComplete(); i++) {
			resultPromise.addToList(i, promises.next());
		}

		if (promises.hasNext()) {
			if (ownership) {
				promises.forEachRemaining(Recyclers::recycle);
			} else {
				Recyclers.recycle(promises);
			}
		}

		return resultPromise.countdown == 0 ? Promise.of(resultPromise.getList()) : resultPromise;
	}

	/**
	 * Returns an array of provided {@code type} and length 0
	 * wrapped in {@code Promise}.
	 */
	@Contract(pure = true)
	public static  Promise toArray(Class type) {
		return Promise.of((T[]) Array.newInstance(type, 0));
	}

	/**
	 * Returns an array with {@code promise1} result.
	 */
	@Contract(pure = true)
	public static  Promise toArray(Class type, Promise promise1) {
		return promise1.map(value -> {
			T[] array = (T[]) Array.newInstance(type, 1);
			array[0] = value;
			return array;
		});
	}

	/**
	 * Returns an array with {@code promise1} and {@code promise2} results.
	 */
	@Contract(pure = true)
	public static  Promise toArray(Class type, Promise promise1, Promise promise2) {
		return promise1.combine(promise2, (value1, value2) -> {
			T[] array = (T[]) Array.newInstance(type, 2);
			array[0] = value1;
			array[1] = value2;
			return array;
		});
	}

	/**
	 * @see Promises#toArray(Class, List)
	 */
	@Contract(pure = true)
	@SafeVarargs
	public static  Promise toArray(Class type, Promise... promises) {
		return toList(promises).map(list -> list.toArray((T[]) Array.newInstance(type, list.size())));
	}

	/**
	 * Reduces promises into Promise<Array>
	 */
	@Contract(pure = true)
	public static  Promise toArray(Class type, List> promises) {
		int size = promises.size();
		if (size == 0) return toArray(type);
		if (size == 1) return toArray(type, promises.get(0));
		if (size == 2) return toArray(type, promises.get(0), promises.get(1));
		return toList(promises).map(list -> list.toArray((T[]) Array.newInstance(type, list.size())));
	}

	/**
	 * @see Promises#toArray(Class, List)
	 */
	@Contract(pure = true)
	public static  Promise toArray(Class type, Stream> promises) {
		return toList(promises).map(list -> list.toArray((T[]) Array.newInstance(type, list.size())));
	}

	/**
	 * @see Promises#toArray(Class, List)
	 */
	@Contract(pure = true)
	public static  Promise toArray(Class type, Iterable> promises) {
		return toList(promises).map(list -> list.toArray((T[]) Array.newInstance(type, list.size())));
	}

	/**
	 * @see Promises#toArray(Class, List)
	 */
	@Contract(pure = true)
	public static  Promise toArray(Class type, Iterator> promises) {
		return toList(promises).map(list -> list.toArray((T[]) Array.newInstance(type, list.size())));
	}

	@Contract(pure = true)
	public static  Promise toTuple(TupleConstructor1 constructor, Promise promise1) {
		return promise1.map(constructor::create);
	}

	@Contract(pure = true)
	public static  Promise toTuple(
		TupleConstructor2 constructor,
		Promise promise1, Promise promise2
	) {
		return promise1.combine(promise2, constructor::create);
	}

	@Contract(pure = true)
	public static  Promise toTuple(
		TupleConstructor3 constructor,
		Promise promise1, Promise promise2, Promise promise3
	) {
		return toList(promise1, promise2, promise3)
			.map(list -> constructor.create((T1) list.get(0), (T2) list.get(1), (T3) list.get(2)));
	}

	@Contract(pure = true)
	public static  Promise toTuple(
		TupleConstructor4 constructor,
		Promise promise1, Promise promise2, Promise promise3,
		Promise promise4
	) {
		return toList(promise1, promise2, promise3, promise4)
			.map(list -> constructor.create((T1) list.get(0), (T2) list.get(1), (T3) list.get(2), (T4) list.get(3)));
	}

	@Contract(pure = true)
	public static  Promise toTuple(
		TupleConstructor5 constructor,
		Promise promise1, Promise promise2, Promise promise3,
		Promise promise4, Promise promise5
	) {
		return toList(promise1, promise2, promise3, promise4, promise5)
			.map(list -> constructor.create((T1) list.get(0), (T2) list.get(1), (T3) list.get(2), (T4) list.get(3), (T5) list.get(4)));
	}

	@Contract(pure = true)
	public static  Promise toTuple(
		TupleConstructor6 constructor,
		Promise promise1, Promise promise2, Promise promise3,
		Promise promise4, Promise promise5, Promise promise6
	) {
		return toList(promise1, promise2, promise3, promise4, promise5, promise6)
			.map(list -> constructor.create((T1) list.get(0), (T2) list.get(1), (T3) list.get(2), (T4) list.get(3), (T5) list.get(4),
				(T6) list.get(5)));
	}

	@Contract(pure = true)
	public static  Promise> toTuple(Promise promise1) {
		return promise1.map(Tuple1::new);
	}

	@Contract(pure = true)
	public static  Promise> toTuple(Promise promise1, Promise promise2) {
		return promise1.combine(promise2, Tuple2::new);
	}

	@Contract(pure = true)
	public static  Promise> toTuple(
		Promise promise1,
		Promise promise2,
		Promise promise3
	) {
		return toList(promise1, promise2, promise3)
			.map(list -> new Tuple3<>((T1) list.get(0), (T2) list.get(1), (T3) list.get(2)));
	}

	@Contract(pure = true)
	public static  Promise> toTuple(
		Promise promise1,
		Promise promise2,
		Promise promise3,
		Promise promise4
	) {
		return toList(promise1, promise2, promise3, promise4)
			.map(list -> new Tuple4<>((T1) list.get(0), (T2) list.get(1), (T3) list.get(2), (T4) list.get(3)));
	}

	@Contract(pure = true)
	public static  Promise> toTuple(
		Promise promise1,
		Promise promise2,
		Promise promise3,
		Promise promise4,
		Promise promise5
	) {
		return toList(promise1, promise2, promise3, promise4, promise5)
			.map(list -> new Tuple5<>((T1) list.get(0), (T2) list.get(1), (T3) list.get(2), (T4) list.get(3), (T5) list.get(4)));
	}

	@Contract(pure = true)
	public static  Promise> toTuple(
		Promise promise1,
		Promise promise2,
		Promise promise3,
		Promise promise4,
		Promise promise5,
		Promise promise6
	) {
		return toList(promise1, promise2, promise3, promise4, promise5, promise6)
			.map(list -> new Tuple6<>((T1) list.get(0), (T2) list.get(1), (T3) list.get(2), (T4) list.get(3), (T5) list.get(4), (T6) list.get(5)));
	}

	@Contract(pure = true)
	public static  AsyncFunction mapTuple(
		TupleConstructor1 constructor,
		Function getter1, Function> fn1
	) {
		return t -> toTuple(constructor,
			fn1.apply(getter1.apply(t)));
	}

	@Contract(pure = true)
	public static  AsyncFunction mapTuple(
		TupleConstructor2 constructor,
		Function getter1, Function> fn1,
		Function getter2, Function> fn2
	) {
		return t -> toTuple(constructor,
			fn1.apply(getter1.apply(t)),
			fn2.apply(getter2.apply(t)));
	}

	@Contract(pure = true)
	public static  AsyncFunction mapTuple(
		TupleConstructor3 constructor,
		Function getter1, Function> fn1,
		Function getter2, Function> fn2,
		Function getter3, Function> fn3
	) {
		return t -> toTuple(constructor,
			fn1.apply(getter1.apply(t)),
			fn2.apply(getter2.apply(t)),
			fn3.apply(getter3.apply(t)));
	}

	@Contract(pure = true)
	public static  AsyncFunction mapTuple(
		TupleConstructor4 constructor,
		Function getter1, Function> fn1,
		Function getter2, Function> fn2,
		Function getter3, Function> fn3,
		Function getter4, Function> fn4
	) {
		return t -> toTuple(constructor,
			fn1.apply(getter1.apply(t)),
			fn2.apply(getter2.apply(t)),
			fn3.apply(getter3.apply(t)),
			fn4.apply(getter4.apply(t)));
	}

	@Contract(pure = true)
	public static  AsyncFunction mapTuple(
		TupleConstructor5 constructor,
		Function getter1, Function> fn1,
		Function getter2, Function> fn2,
		Function getter3, Function> fn3,
		Function getter4, Function> fn4,
		Function getter5, Function> fn5
	) {
		return t -> toTuple(constructor,
			fn1.apply(getter1.apply(t)),
			fn2.apply(getter2.apply(t)),
			fn3.apply(getter3.apply(t)),
			fn4.apply(getter4.apply(t)),
			fn5.apply(getter5.apply(t)));
	}

	@Contract(pure = true)
	public static  AsyncFunction mapTuple(
		TupleConstructor6 constructor,
		Function getter1, Function> fn1,
		Function getter2, Function> fn2,
		Function getter3, Function> fn3,
		Function getter4, Function> fn4,
		Function getter5, Function> fn5,
		Function getter6, Function> fn6
	) {
		return t -> toTuple(constructor,
			fn1.apply(getter1.apply(t)),
			fn2.apply(getter2.apply(t)),
			fn3.apply(getter3.apply(t)),
			fn4.apply(getter4.apply(t)),
			fn5.apply(getter5.apply(t)),
			fn6.apply(getter6.apply(t)));
	}

	/**
	 * Returns a {@link CompleteNullPromise}
	 */
	public static Promise sequence() {
		return Promise.complete();
	}

	/**
	 * Executes an {@link AsyncRunnable}, returning a {@link Promise}
	 * as a mark for completion
	 */
	public static Promise sequence(AsyncRunnable runnable) {
		return runnable.run();
	}

	/**
	 * Executes both {@link AsyncRunnable}s consequently, returning a {@link Promise}
	 * as a mark for completion
	 */
	public static Promise sequence(AsyncRunnable runnable1, AsyncRunnable runnable2) {
		return runnable1.run().then(runnable2::run);
	}

	/**
	 * @see Promises#sequence(Iterator)
	 */
	public static Promise sequence(AsyncRunnable... runnables) {
		return sequence(List.of(runnables));
	}

	/**
	 * @see Promises#sequence(Iterator)
	 */
	public static Promise sequence(Iterable runnables) {
		return sequence(transformIterator(runnables.iterator(), AsyncRunnable::run));
	}

	/**
	 * @see Promises#sequence(Iterator)
	 */
	public static Promise sequence(Stream runnables) {
		return sequence(transformIterator(runnables.iterator(), AsyncRunnable::run));
	}

	/**
	 * Calls every {@code Promise} from {@code promises} in sequence and discards
	 * their results.Returns a {@code SettablePromise} with {@code null} result as
	 * a marker when all of the {@code promises} are completed.
	 *
	 * @return {@code Promise} that completes when all {@code promises} are completed
	 */
	public static Promise sequence(Iterator> promises) {
		return Promise.ofCallback(cb ->
			sequenceImpl(promises, cb));
	}

	private static void sequenceImpl(Iterator> promises, SettableCallback cb) {
		while (promises.hasNext()) {
			Promise promise = promises.next();
			if (promise.isResult()) continue;
			promise.subscribe((result, e) -> {
				if (e == null) {
					sequenceImpl(promises, cb);
				} else {
					cb.setException(e);
				}
			});
			return;
		}
		cb.set(null);
	}

	/**
	 * Picks the first {@code Promise} that was completed without exception.
	 *
	 * @see Promises#first(BiPredicate, Iterator)
	 */
	@SafeVarargs
	public static  Promise first(AsyncSupplier... promises) {
		return first(isResult(), promises);
	}

	/**
	 * @see #first(AsyncSupplier[])
	 * @see Promises#first(BiPredicate, Iterator)
	 */
	public static  Promise first(Iterable> promises) {
		return first(isResult(), promises);
	}

	/**
	 * @see #first(AsyncSupplier[])
	 * @see Promises#first(BiPredicate, Iterator)
	 */
	public static  Promise first(Stream> promises) {
		return first(isResult(), promises);
	}

	/**
	 * @see #first(AsyncSupplier[])
	 * @see Promises#first(BiPredicate, Iterator)
	 */
	public static  Promise first(Iterator> promises) {
		return first(isResult(), promises);
	}

	/**
	 * @see Promises#first(BiPredicate, Iterator)
	 */
	@SafeVarargs
	public static  Promise first(
		BiPredicate predicate, AsyncSupplier... promises
	) {
		return first(predicate, List.of(promises));
	}

	/**
	 * @see Promises#first(BiPredicate, Iterator)
	 */
	public static  Promise first(
		BiPredicate predicate, Iterable> promises
	) {
		return first(predicate, asPromises(promises));
	}

	/**
	 * @see Promises#first(BiPredicate, Iterator)
	 */
	public static  Promise first(
		BiPredicate predicate,
		Stream> promises
	) {
		return first(predicate, asPromises(promises));
	}

	/**
	 * @param predicate filters results, consumes result of {@code Promise}
	 * @return first completed result of {@code Promise} that satisfies predicate
	 */
	public static  Promise first(
		BiPredicate predicate,
		Iterator> promises
	) {
		return Promise.ofCallback(cb ->
			firstImpl(promises, predicate, cb));
	}

	private static  void firstImpl(
		Iterator> promises, BiPredicate predicate,
		SettableCallback cb
	) {
		while (promises.hasNext()) {
			Promise nextPromise = promises.next();
			if (nextPromise.isComplete()) {
				T v = nextPromise.getResult();
				Exception e = nextPromise.getException();
				if (predicate.test(v, e)) {
					cb.set(v, e);
					return;
				}
				Recyclers.recycle(v);
				continue;
			}
			nextPromise.subscribe((v, e) -> {
				if (predicate.test(v, e)) {
					cb.set(v, e);
				} else {
					Recyclers.recycle(v);
					firstImpl(promises, predicate, cb);
				}
			});
			return;
		}
		cb.setException(new Exception("There are no promises to be complete"));
	}

	/**
	 * Repeats the operations of provided {@link AsyncSupplier} infinitely,
	 * until one of the {@code Promise}s completes exceptionally or supplier returns a promise of {@code false}.
	 */
	public static Promise repeat(AsyncSupplier supplier) {
		SettablePromise cb = new SettablePromise<>();
		repeatImpl(supplier, cb);
		return cb;
	}

	private static void repeatImpl(AsyncSupplier supplier, SettablePromise cb) {
		while (true) {
			Promise promise = supplier.get();
			if (promise.isResult()) {
				if (promise.getResult() == Boolean.TRUE) continue;
				cb.set(null);
				break;
			}
			promise.subscribe((b, e) -> {
				if (e == null) {
					if (b == Boolean.TRUE) {
						repeatImpl(supplier, cb);
					} else {
						cb.set(null);
					}
				} else {
					cb.setException(e);
				}
			});
			break;
		}
	}

	/**
	 * Repeats provided {@link FunctionEx} until can pass {@link Predicate} test.
	 * Resembles a simple Java {@code for()} loop but with async capabilities.
	 *
	 * @param seed          start value
	 * @param loopCondition a boolean function which checks if this loop can continue
	 * @param next          a function applied to the seed, returns {@code Promise}
	 * @return {@link SettablePromise} with {@code null} result if it was
	 * completed successfully, otherwise returns a {@code SettablePromise}
	 * with an exception. In both situations returned {@code Promise}
	 * is a marker of completion of the loop.
	 */
	public static  Promise loop(@Nullable T seed, Predicate loopCondition, FunctionEx> next) {
		if (!loopCondition.test(seed)) return Promise.of(seed);
		return until(seed, next, v -> !loopCondition.test(v));
	}

	public static  Promise until(@Nullable T seed, FunctionEx> next, Predicate breakCondition) {
		return Promise.ofCallback(cb ->
			untilImpl(seed, next, breakCondition, cb));
	}

	private static  void untilImpl(@Nullable T value, FunctionEx> next, Predicate breakCondition, SettableCallback cb) throws Exception {
		while (true) {
			Promise promise = next.apply(value);
			if (promise.isResult()) {
				value = promise.getResult();
				if (breakCondition.test(value)) {
					cb.set(value);
					break;
				}
			} else {
				promise.subscribe((newValue, e) -> {
					if (e == null) {
						if (breakCondition.test(newValue)) {
							cb.set(newValue);
						} else {
							try {
								untilImpl(newValue, next, breakCondition, cb);
							} catch (Exception ex) {
								handleError(ex, next);
								cb.setException(ex);
							}
						}
					} else {
						cb.setException(e);
					}
				});
				return;
			}
		}
	}

	public static  Promise retry(AsyncSupplier supplier) {
		return retry(isResult(), supplier);
	}

	public static  Promise retry(BiPredicate breakCondition, AsyncSupplier supplier) {
		return first(breakCondition, Stream.generate(() -> supplier));
	}

	public static  Promise retry(AsyncSupplier supplier, RetryPolicy retryPolicy) {
		return retry(supplier, (v, e) -> e == null, retryPolicy);
	}

	public static  Promise retry(AsyncSupplier supplier, BiPredicate breakCondition, RetryPolicy retryPolicy) {
		return Promise.ofCallback(cb ->
			retryImpl(supplier, breakCondition, (RetryPolicy) retryPolicy, null, cb));
	}

	private static  void retryImpl(
		AsyncSupplier next, BiPredicate breakCondition, RetryPolicy retryPolicy,
		Object retryState, SettableCallback cb
	) {
		next.get()
			.subscribe((v, e) -> {
				if (breakCondition.test(v, e)) {
					cb.set(v, e);
				} else {
					Reactor reactor = getCurrentReactor();
					long now = reactor.currentTimeMillis();
					Object retryStateFinal = retryState != null ? retryState : retryPolicy.createRetryState();
					long nextRetryTimestamp = retryPolicy.nextRetryTimestamp(now, e, retryStateFinal);
					if (nextRetryTimestamp == 0) {
						cb.setException(e != null ? e : new Exception("RetryPolicy: giving up " + retryState));
					} else {
						reactor.schedule(nextRetryTimestamp,
							() -> retryImpl(next, breakCondition, retryPolicy, retryStateFinal, cb));
					}
				}
			});
	}

	/**
	 * Transforms a collection of {@link AsyncSupplier}
	 * {@code tasks} to a collection of {@code Promise}s.
	 */
	public static  Iterator> asPromises(Iterator> tasks) {
		return transformIterator((Iterator>) tasks, AsyncSupplier::get);
	}

	/**
	 * Transforms a {@link Stream} of {@link AsyncSupplier}
	 * {@code tasks} to a collection of {@code Promise}s.
	 */
	public static  Iterator> asPromises(Stream> tasks) {
		return asPromises(tasks.iterator());
	}

	/**
	 * Transforms an {@link Iterable} of {@link AsyncSupplier}
	 * {@code tasks} to a collection of {@code Promise}s.
	 */
	public static  Iterator> asPromises(Iterable> tasks) {
		return asPromises(tasks.iterator());
	}

	/**
	 * Transforms an {@link AsyncSupplier} {@code tasks}
	 * to a collection of {@code Promise}s.
	 */
	@SafeVarargs
	public static  Iterator> asPromises(AsyncSupplier... tasks) {
		return asPromises(iteratorOf(tasks));
	}

	/**
	 * @see #reduce(A, BiConsumerEx, FunctionEx, Iterator)
	 */
	public static  Promise reduce(@Nullable A accumulator, BiConsumerEx combiner, FunctionEx finisher, Collection> promises) {
		return reduce(accumulator, combiner, finisher, promises.iterator());
	}

	/**
	 * @see #reduce(A, BiConsumerEx, FunctionEx, Iterator)
	 */
	public static  Promise reduce(@Nullable A accumulator, BiConsumerEx combiner, FunctionEx finisher, Stream> promises) {
		return reduce(accumulator, combiner, finisher, promises.iterator());
	}

	/**
	 * Asynchronously reduce {@link Iterator} of {@link Promise}s into a {@link Promise}.
	 * 

* To reduce promises a following arguments should be supplied an accumulator, a combiner, * a finisher and an iterator of promises *

* Reduction principle is somewhat similar to the {@link Collector#of(Supplier, BiConsumer, BinaryOperator, Function, Collector.Characteristics...)} *

* If one of the {@link Promise}s completes exceptionally, a resulting promise will be completed exceptionally as well. * * @param accumulator a promise result accumulator that holds intermediate promise results * @param combiner a combiner consumer that defines how promise results should be accumulated * @param finisher a finisher function that maps an accumulator to a result value * @param promises {@code Iterator} of {@code Promise}s * @param type of input elements for this operation * @param type of accumulator of intermediate promise results * @param the result type of the reduction * @return a {@code Promise} of the accumulated result of the reduction. */ public static Promise reduce(@Nullable A accumulator, BiConsumerEx combiner, FunctionEx finisher, Iterator> promises) { AsyncAccumulator asyncAccumulator = AsyncAccumulator.create(accumulator); while (promises.hasNext()) { asyncAccumulator.addPromise(promises.next(), combiner); } return asyncAccumulator.run().map(finisher); } /** * Asynchronously reduce {@link Iterator} of {@link Promise}s into a {@link Promise} * with the help of {@link Collector}. *

* You can control the amount of concurrently running {@code Promise}s. *

* If one of the {@link Promise}s completes exceptionally, a resulting promise will be completed exceptionally as well. *

* This method is universal and allows implementing app-specific logic. * * @param collector mutable reduction operation that accumulates input * elements into a mutable result container * @param maxCalls max number of concurrently running {@code Promise}s * @param promises {@code Iterable} of {@code Promise}s * @param type of input elements for this operation * @param type of accumulator of intermediate promise results * @param the result type of the reduction * @return a {@code Promise} of the accumulated result of the reduction. */ public static Promise reduce( Collector collector, int maxCalls, Iterator> promises ) { return reduce(collector.supplier().get(), BiConsumerEx.of(collector.accumulator()), FunctionEx.of(collector.finisher()), maxCalls, promises); } /** * Asynchronously reduce {@link Iterator} of {@link Promise}s into a {@link Promise}. *

* To reduce promises a following arguments should be supplied an accumulator, a combiner, * a finisher and an iterator of promises *

* You can control the amount of concurrently running {@code Promise}s. *

* Reduction principle is somewhat similar to the {@link Collector#of(Supplier, BiConsumer, BinaryOperator, Function, Collector.Characteristics...)} *

* If one of the {@link Promise}s completes exceptionally, a resulting promise will be completed exceptionally as well. * * @param accumulator a promise result accumulator that holds intermediate promise results * @param combiner a combiner consumer that defines how promise results should be accumulated * @param finisher a finisher function that maps an accumulator to a result value * @param maxCalls max number of concurrently running {@code Promise}s * @param promises {@code Iterator} of {@code Promise}s * @param type of input elements for this operation * @param type of accumulator of intermediate promise results * @param the result type of the reduction * @return a {@code Promise} of the accumulated result of the reduction. */ public static Promise reduce(@Nullable A accumulator, BiConsumerEx combiner, FunctionEx finisher, int maxCalls, Iterator> promises) { AsyncAccumulator asyncAccumulator = AsyncAccumulator.create(accumulator); for (int i = 0; promises.hasNext() && i < maxCalls; i++) { reduceImpl(asyncAccumulator, combiner, promises); } return asyncAccumulator.run().map(finisher); } private static void reduceImpl(AsyncAccumulator accumulator, BiConsumerEx combiner, Iterator> promises) { while (promises.hasNext()) { Promise promise = promises.next(); if (promise.isComplete()) { accumulator.addPromise(promise, combiner); } else { accumulator.addPromise( promise.whenResult(() -> reduceImpl(accumulator, combiner, promises)), combiner); break; } } } @Contract(pure = true) public static AsyncFunction coalesce( Supplier argumentAccumulatorSupplier, BiConsumer argumentAccumulatorFn, AsyncFunction fn ) { AsyncBuffer buffer = new AsyncBuffer<>(fn, argumentAccumulatorSupplier); return v -> { Promise promise = buffer.add(argumentAccumulatorFn, v); if (!buffer.isActive()) { repeat(() -> buffer.flush().map($ -> buffer.isBuffered())); } return promise; }; } // region helper classes private static final class PromiseAll extends NextPromise { int countdown = 1; @Override public void acceptNext(@Nullable T result, @Nullable Exception e) { if (e == null) { Recyclers.recycle(result); if (--countdown == 0) { complete(null); } } else { tryCompleteExceptionally(e); } } @Override protected String describe() { return "Promises.all()"; } } private static final class PromiseAny extends NextPromise { private final BiPredicate predicate; int countdown = 1; private PromiseAny(BiPredicate predicate) { this.predicate = predicate; } @Override public void acceptNext(@Nullable T result, @Nullable Exception e) { if (predicate.test(result, e)) { if (!tryComplete(result, e)) { Recyclers.recycle(result); } } else { Recyclers.recycle(result); if (--countdown == 0) { completeExceptionally(new Exception("There are no promises to be complete")); } } } @Override protected String describe() { return "Promises.any()"; } } private static final class PromisesToList extends AbstractPromise> { Object[] array; int countdown; int size; private PromisesToList(int initialSize) { this.array = new Object[initialSize]; } private void addToList(int i, Promise promise) { ensureSize(i + 1); if (promise.isResult()) { if (!isComplete()) { array[i] = promise.getResult(); } else { Recyclers.recycle(result); } } else if (promise.isException()) { if (tryCompleteExceptionally(promise.getException())) { Recyclers.recycle(array); } } else { countdown++; promise.subscribe((result, e) -> { if (e == null) { if (!isComplete()) { array[i] = result; if (--countdown == 0) { complete(getList()); } } else { Recyclers.recycle(result); } } else { if (tryCompleteExceptionally(e)) { Recyclers.recycle(array); } } }); } } private void ensureSize(int size) { this.size = size; if (size >= array.length) { array = Arrays.copyOf(array, array.length * 2); } } private List getList() { return (List) asList(size == this.array.length ? this.array : Arrays.copyOf(this.array, size)); } @Override protected String describe() { return "Promises.toList()"; } } // endregion }