ps-promise 0.1.0-17

Promise-like owned futures
Documentation
use std::future::Future;

use crate::{Promise, PromiseRejection};

impl<T, E> Promise<T, E>
where
    T: Send + 'static,
    E: PromiseRejection,
{
    /// Wraps a [`Future`] in a [`Promise`] eagerly scheduled via `tokio::spawn`
    /// or `smol::spawn`.
    ///
    /// Dispatch is selected at compile time based on which runtime features
    /// are enabled, with a runtime check when `tokio` is on:
    ///
    /// - Only `tokio` enabled: dispatches to `Promise::eager_with_tokio` when
    ///   called from within a tokio runtime context (detected via
    ///   `tokio::runtime::Handle::try_current`), otherwise falls back to
    ///   [`Promise::lazy`], so the future only progresses when the [`Promise`]
    ///   is polled.
    /// - Only `smol` enabled: always dispatches to `Promise::eager_with_smol`.
    /// - Both enabled: dispatches to `Promise::eager_with_tokio` when called
    ///   from within a tokio runtime context, otherwise to
    ///   `Promise::eager_with_smol`.
    ///
    /// Requires at least one of the `tokio` or `smol` features; if neither is
    /// enabled this method does not exist and call sites fail to compile.
    pub fn eager(future: impl Future<Output = Result<T, E>> + Send + 'static) -> Self {
        #[cfg(all(feature = "tokio", feature = "smol"))]
        return if tokio::runtime::Handle::try_current().is_ok() {
            Self::eager_with_tokio(future)
        } else {
            Self::eager_with_smol(future)
        };

        #[cfg(all(feature = "tokio", not(feature = "smol")))]
        return if tokio::runtime::Handle::try_current().is_ok() {
            Self::eager_with_tokio(future)
        } else {
            Self::lazy(future)
        };

        #[cfg(all(feature = "smol", not(feature = "tokio")))]
        return Self::eager_with_smol(future);
    }
}

#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
    use crate::{Promise, PromiseRejection, TaskFailure};

    #[derive(Debug)]
    #[allow(dead_code)]
    enum E {
        AlreadyConsumed,
        TaskFailed(TaskFailure),
    }

    impl PromiseRejection for E {
        fn already_consumed() -> Self {
            Self::AlreadyConsumed
        }

        fn task_failed(failure: TaskFailure) -> Self {
            Self::TaskFailed(failure)
        }
    }

    #[cfg(all(feature = "tokio", not(feature = "smol")))]
    #[test]
    fn falls_back_to_lazy_outside_runtime_context() {
        use std::task::{Context, Waker};

        let mut promise: Promise<i32, E> = Promise::eager(async { Ok(42) });

        assert!(promise.is_pending());

        promise.poll(&mut Context::from_waker(Waker::noop()));

        match promise.consume() {
            Some(Ok(v)) => assert_eq!(v, 42),
            other => panic!("expected Resolved(42), got {other:?}"),
        }
    }

    #[cfg(feature = "tokio")]
    #[test]
    fn resolves_value_via_tokio() {
        let rt = tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build current-thread tokio runtime");

        let result = rt.block_on(async { Promise::<i32, E>::eager(async { Ok(42) }).await });

        assert!(matches!(result, Ok(42)));
    }

    #[cfg(all(feature = "smol", not(feature = "tokio")))]
    #[test]
    fn resolves_value_via_smol() {
        let result = smol::block_on(async { Promise::<i32, E>::eager(async { Ok(42) }).await });

        assert!(matches!(result, Ok(42)));
    }
}