mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-27 21:00:50 +00:00
Unbacked out changeset 19fe1e621c5c (bug 966182) because it didn't actually introduce the new hazard.
This commit is contained in:
parent
21932fdf4b
commit
af2cd64053
@ -442,6 +442,14 @@ Promise.defer = function ()
|
||||
*/
|
||||
Promise.resolve = function (aValue)
|
||||
{
|
||||
if (aValue && typeof(aValue) == "function" && aValue.isAsyncFunction) {
|
||||
throw new TypeError(
|
||||
"Cannot resolve a promise with an async function. " +
|
||||
"You should either invoke the async function first " +
|
||||
"or use 'Task.spawn' instead of 'Task.async' to start " +
|
||||
"the Task and return its promise.");
|
||||
}
|
||||
|
||||
return new Promise((aResolve) => aResolve(aValue));
|
||||
};
|
||||
|
||||
|
@ -136,24 +136,53 @@ this.Task = {
|
||||
* called when the task terminates.
|
||||
*/
|
||||
spawn: function Task_spawn(aTask) {
|
||||
if (aTask && typeof(aTask) == "function") {
|
||||
try {
|
||||
// Let's call into the function ourselves.
|
||||
aTask = aTask();
|
||||
} catch (ex if ex instanceof Task.Result) {
|
||||
return Promise.resolve(ex.value);
|
||||
} catch (ex) {
|
||||
return Promise.reject(ex);
|
||||
}
|
||||
return createAsyncFunction(aTask).call(undefined);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create and return an 'async function' that starts a new task.
|
||||
*
|
||||
* This is similar to 'spawn' except that it doesn't immediately start
|
||||
* the task, it binds the task to the async function's 'this' object and
|
||||
* arguments, and it requires the task to be a function.
|
||||
*
|
||||
* It simplifies the common pattern of implementing a method via a task,
|
||||
* like this simple object with a 'greet' method that has a 'name' parameter
|
||||
* and spawns a task to send a greeting and return its reply:
|
||||
*
|
||||
* let greeter = {
|
||||
* message: "Hello, NAME!",
|
||||
* greet: function(name) {
|
||||
* return Task.spawn((function* () {
|
||||
* return yield sendGreeting(this.message.replace(/NAME/, name));
|
||||
* }).bind(this);
|
||||
* })
|
||||
* };
|
||||
*
|
||||
* With Task.async, the method can be declared succinctly:
|
||||
*
|
||||
* let greeter = {
|
||||
* message: "Hello, NAME!",
|
||||
* greet: Task.async(function* (name) {
|
||||
* return yield sendGreeting(this.message.replace(/NAME/, name));
|
||||
* })
|
||||
* };
|
||||
*
|
||||
* While maintaining identical semantics:
|
||||
*
|
||||
* greeter.greet("Mitchell").then((reply) => { ... }); // behaves the same
|
||||
*
|
||||
* @param aTask
|
||||
* The task function to start.
|
||||
*
|
||||
* @return A function that starts the task function and returns its promise.
|
||||
*/
|
||||
async: function Task_async(aTask) {
|
||||
if (typeof(aTask) != "function") {
|
||||
throw new TypeError("aTask argument must be a function");
|
||||
}
|
||||
|
||||
if (isGenerator(aTask)) {
|
||||
// This is an iterator resulting from calling a generator function.
|
||||
return new TaskImpl(aTask).deferred.promise;
|
||||
}
|
||||
|
||||
// Just propagate the given value to the caller as a resolved promise.
|
||||
return Promise.resolve(aTask);
|
||||
return createAsyncFunction(aTask);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -168,6 +197,42 @@ this.Task = {
|
||||
}
|
||||
};
|
||||
|
||||
function createAsyncFunction(aTask) {
|
||||
let asyncFunction = function () {
|
||||
let result = aTask;
|
||||
if (aTask && typeof(aTask) == "function") {
|
||||
if (aTask.isAsyncFunction) {
|
||||
throw new TypeError(
|
||||
"Cannot use an async function in place of a promise. " +
|
||||
"You should either invoke the async function first " +
|
||||
"or use 'Task.spawn' instead of 'Task.async' to start " +
|
||||
"the Task and return its promise.");
|
||||
}
|
||||
|
||||
try {
|
||||
// Let's call into the function ourselves.
|
||||
result = aTask.apply(this, arguments);
|
||||
} catch (ex if ex instanceof Task.Result) {
|
||||
return Promise.resolve(ex.value);
|
||||
} catch (ex) {
|
||||
return Promise.reject(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (isGenerator(result)) {
|
||||
// This is an iterator resulting from calling a generator function.
|
||||
return new TaskImpl(result).deferred.promise;
|
||||
}
|
||||
|
||||
// Just propagate the given value to the caller as a resolved promise.
|
||||
return Promise.resolve(result);
|
||||
};
|
||||
|
||||
asyncFunction.isAsyncFunction = true;
|
||||
|
||||
return asyncFunction;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// TaskImpl
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
Components.utils.import("resource://gre/modules/Promise.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Test runner
|
||||
@ -527,6 +528,14 @@ tests.push(
|
||||
return promise;
|
||||
}));
|
||||
|
||||
// Test that Promise.resolve throws when its argument is an async function.
|
||||
tests.push(
|
||||
make_promise_test(function test_promise_resolve_throws_with_async_function(test) {
|
||||
Assert.throws(() => Promise.resolve(Task.async(function* () {})),
|
||||
/Cannot resolve a promise with an async function/);
|
||||
return Promise.resolve();
|
||||
}));
|
||||
|
||||
// Test that the code after "then" is always executed before the callbacks
|
||||
tests.push(
|
||||
make_promise_test(function then_returns_before_callbacks(test) {
|
||||
|
@ -118,6 +118,36 @@ add_test(function test_spawn_function()
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_spawn_function_this()
|
||||
{
|
||||
Task.spawn(function () {
|
||||
return this;
|
||||
}).then(function (result) {
|
||||
// Since the task function wasn't defined in strict mode, its "this" object
|
||||
// should be the same as the "this" object in this function, i.e. the global
|
||||
// object.
|
||||
do_check_eq(result, this);
|
||||
run_next_test();
|
||||
}, function (ex) {
|
||||
do_throw("Unexpected error: " + ex);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_spawn_function_this_strict()
|
||||
{
|
||||
"use strict";
|
||||
Task.spawn(function () {
|
||||
return this;
|
||||
}).then(function (result) {
|
||||
// Since the task function was defined in strict mode, its "this" object
|
||||
// should be undefined.
|
||||
do_check_eq(typeof(result), "undefined");
|
||||
run_next_test();
|
||||
}, function (ex) {
|
||||
do_throw("Unexpected error: " + ex);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_spawn_function_returning_promise()
|
||||
{
|
||||
Task.spawn(function () {
|
||||
@ -242,3 +272,116 @@ add_test(function test_mixed_legacy_and_star()
|
||||
do_throw("Unexpected error: " + ex);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_async_function_from_generator()
|
||||
{
|
||||
Task.spawn(function* () {
|
||||
let object = {
|
||||
asyncFunction: Task.async(function* (param) {
|
||||
do_check_eq(this, object);
|
||||
return param;
|
||||
})
|
||||
};
|
||||
|
||||
// Ensure the async function returns a promise that resolves as expected.
|
||||
do_check_eq((yield object.asyncFunction(1)), 1);
|
||||
|
||||
// Ensure a second call to the async function also returns such a promise.
|
||||
do_check_eq((yield object.asyncFunction(3)), 3);
|
||||
}).then(function () {
|
||||
run_next_test();
|
||||
}, function (ex) {
|
||||
do_throw("Unexpected error: " + ex);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_async_function_from_function()
|
||||
{
|
||||
Task.spawn(function* () {
|
||||
return Task.spawn(function* () {
|
||||
let object = {
|
||||
asyncFunction: Task.async(function (param) {
|
||||
do_check_eq(this, object);
|
||||
return param;
|
||||
})
|
||||
};
|
||||
|
||||
// Ensure the async function returns a promise that resolves as expected.
|
||||
do_check_eq((yield object.asyncFunction(5)), 5);
|
||||
|
||||
// Ensure a second call to the async function also returns such a promise.
|
||||
do_check_eq((yield object.asyncFunction(7)), 7);
|
||||
});
|
||||
}).then(function () {
|
||||
run_next_test();
|
||||
}, function (ex) {
|
||||
do_throw("Unexpected error: " + ex);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_async_function_that_throws_rejects_promise()
|
||||
{
|
||||
Task.spawn(function* () {
|
||||
let object = {
|
||||
asyncFunction: Task.async(function* () {
|
||||
throw "Rejected!";
|
||||
})
|
||||
};
|
||||
|
||||
yield object.asyncFunction();
|
||||
}).then(function () {
|
||||
do_throw("unexpected success calling async function that throws error");
|
||||
}, function (ex) {
|
||||
do_check_eq(ex, "Rejected!");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_async_return_function()
|
||||
{
|
||||
Task.spawn(function* () {
|
||||
// Ensure an async function that returns a function resolves to the function
|
||||
// itself instead of calling the function and resolving to its return value.
|
||||
return Task.spawn(function* () {
|
||||
let returnValue = function () {
|
||||
return "These aren't the droids you're looking for.";
|
||||
};
|
||||
|
||||
let asyncFunction = Task.async(function () {
|
||||
return returnValue;
|
||||
});
|
||||
|
||||
do_check_eq((yield asyncFunction()), returnValue);
|
||||
});
|
||||
}).then(function () {
|
||||
run_next_test();
|
||||
}, function (ex) {
|
||||
do_throw("Unexpected error: " + ex);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_async_throw_argument_not_function()
|
||||
{
|
||||
Task.spawn(function* () {
|
||||
// Ensure Task.async throws if its aTask argument is not a function.
|
||||
Assert.throws(() => Task.async("not a function"),
|
||||
/aTask argument must be a function/);
|
||||
}).then(function () {
|
||||
run_next_test();
|
||||
}, function (ex) {
|
||||
do_throw("Unexpected error: " + ex);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_async_throw_on_function_in_place_of_promise()
|
||||
{
|
||||
Task.spawn(function* () {
|
||||
// Ensure Task.spawn throws if passed an async function.
|
||||
Assert.throws(() => Task.spawn(Task.async(function* () {})),
|
||||
/Cannot use an async function in place of a promise/);
|
||||
}).then(function () {
|
||||
run_next_test();
|
||||
}, function (ex) {
|
||||
do_throw("Unexpected error: " + ex);
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user