1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
use chakracore_sys::*;
use context::ContextGuard;
use super::{Value, Object, Function};
use error::*;
use Context;

/// A JavaScript promise executor.
pub struct Executor {
    resolve: Function,
    reject: Function,
}

impl Executor {
    /// Consumes the `Executor` and fulfills the associated promise.
    pub fn resolve(self, guard: &ContextGuard, arguments: &[&Value]) -> Result<()> {
        self.resolve.call(guard, arguments).map(|_| ())
    }

    /// Consumes the `Executor` and rejects the associated promise.
    pub fn reject(self, guard: &ContextGuard, arguments: &[&Value]) -> Result<()> {
        self.reject.call(guard, arguments).map(|_| ())
    }
}

/// A JavaScript promise.
///
/// To support promises within a context, see [Context](../context/struct.Context.html).
pub struct Promise(JsValueRef);

impl Promise {
    /// Creates a new promise with an associated executor.
    pub fn new(_guard: &ContextGuard) -> (Self, Executor) {
        let mut reference = JsValueRef::new();
        let mut resolve = JsValueRef::new();
        let mut reject = JsValueRef::new();

        unsafe {
            jsassert!(JsCreatePromise(&mut reference, &mut resolve, &mut reject));
            (Self::from_raw(reference), Executor {
                resolve: Function::from_raw(resolve),
                reject: Function::from_raw(reject)
            })
        }
    }

    /// Returns true if the value is a `Promise`.
    pub fn is_same(value: &Value) -> bool {
        // See: https://github.com/Microsoft/ChakraCore/issues/135
        // There is no straight foward way to do this with the current API.
        value.clone().into_object().map_or(false, |object| {
            Context::exec_with_value(&object, |guard| {
                let promise = ::script::eval(guard, "Promise")
                    .expect("retrieving Promise constructor")
                    .into_function()
                    .expect("converting Promise to function");
                promise.instance_of(guard, &object)
            })
            .expect("changing active context for Promise comparison")
            .expect("missing associated context for Promise comparison")
        })
    }
}

reference!(Promise);
inherit!(Promise, Object);
subtype!(Promise, Value);

#[cfg(test)]
mod tests {
    use {test, value, script, Property};

    #[test]
    fn resolve() {
        test::run_with_context(|guard| {
            let (promise, executor) = value::Promise::new(guard);
            executor.resolve(guard, &[&value::Number::new(guard, 10)]).unwrap();

            let global = guard.global();
            let property = Property::new(guard, "promise");
            global.set(guard, &property, &promise);

            let result = script::eval(guard, "
                var result = {};
                promise.then(function(value) { result.val = value; });
                result")
                .unwrap()
                .into_object()
                .unwrap();
            guard.execute_tasks();
            assert_eq!(result.get(guard, &Property::new(guard, "val")).to_integer(guard), 10);
        });
    }

    #[test]
    fn conversion() {
        test::run_with_context(|guard| {
            let (promise, _) = value::Promise::new(guard);
            let value: value::Value = promise.into();
            assert!(value.into_promise().is_some());

            let promise = script::eval(guard, "new Promise(() => {})")
                .unwrap()
                .into_promise();
            assert!(promise.is_some());
        });
    }
}