Created
December 3, 2010 17:10
-
-
Save getify/727232 to your computer and use it in GitHub Desktop.
theoretical native promise/defer via @ operator (in JavaScript or something like it)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//SUMMARY: | |
// a() @ b() ==> execute a(). if a() flags an async deferral inside it, | |
// then wait to continue execution of the expression until that promise is | |
// fulfilled, then continue execution *AT* b(). | |
// | |
// more generally: X @ Y ==> evaluate X expression. if it was a function call | |
// call that deferred with a promise, wait until fulfilled then continue at | |
// Y. otherwise, assume that X had an implicit immediately fulfilled promise, | |
// and continue evaluating at Y. | |
// ------ | |
// basically, the idea is that every function should be able to flag a "state" | |
// that is inspectable (by the operator) after the function's call completes, | |
// and that state (the "promise") indicates if the promise is immediately | |
// fulfilled, or if it's deferred to fulfill later. The @ operator inspects | |
// this special state of the function call to determine if it should wait or | |
// proceed with evaluation of the rest of the statement expression. | |
// | |
// NOTE: by modeling a function's promise as a special internal state of the | |
// function, rather than conflating it with the function's return value, the | |
// function can also return any value immediately, even if it's ultimately | |
// deferred to complete later. | |
function foo() { | |
setTimeout(function(){ | |
blah++; | |
console.log(blah); | |
},1000); | |
} | |
var blah = 1; | |
foo(); blah = 10; foo(); // expected output: 11, 12 | |
---------------- | |
function foo() { | |
var p = promise; // `promise` being new auto keyword kinda like `arguments` | |
setTimeout(function(){ | |
blah++; | |
console.log(blah); | |
p.fulfill(); | |
},1000); | |
p.defer(); // flag this function as needing to defer its promise | |
} | |
var blah = 1; | |
foo() @ (blah = 10); foo(); // expected output: 2, 11 | |
blah = 1; | |
foo() @ (blah = 10) @ foo(); // expected output: 2, 11 | |
---------------- | |
function foo() { | |
var p = promise; // `promise` being new auto keyword kinda like `arguments` | |
setTimeout(function(){ | |
blah++; | |
console.log(blah); | |
p.fulfill(); | |
},1000); | |
p.defer(); // flag this function as needing to defer its promise | |
} | |
var blah; | |
(blah = 5) @ foo() @ (blah = 10) @ foo(); blah = 100; // expected output: 101, 11 | |
---------------- | |
function foo() { | |
var p = promise; // `promise` being new auto keyword kinda like `arguments` | |
console.log(blah); | |
setTimeout(function(){ | |
blah++; | |
console.log(blah); | |
p.fulfill(); | |
},1000); | |
p.defer(); // flag this function as needing to defer its promise | |
} | |
var blah; | |
(blah = 5) @ foo(); blah = 100; // expected output: 5, 101 | |
blah = 5; foo(); blah = 100; // expected output: 5, 101 | |
blah = 5; foo(); blah = 100; foo(); // expected output: 5, 100, 101, 102 | |
(blah = 5) @ foo() @ (blah = 100) @ foo(); // expected output: 5, 6, 100, 101 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function foo() { | |
var p = promise; // `promise` being new auto keyword kinda like `arguments` | |
setTimeout(function(){ | |
if (blah == 5) { | |
p.fulfill(); | |
} | |
else { | |
p.fail(); | |
} | |
},1000); | |
p.defer(); // flag this function as needing to defer its promise | |
} | |
function yay() { | |
console.log("Yay, blah is: "+blah); | |
} | |
function bummer() { | |
console.log("Bummer, blah is: "+blah); | |
} | |
var blah = 5; | |
foo() @ yay() : bummer(); // Yay, blah is: 5 | |
foo() @ yay() : bummer(); blah = 10; // Bummer, blah is: 10 | |
(blah = 5) @ foo() @ yay() : bummer(); // Yay, blah is: 5 | |
blah = 5; | |
foo() @ (blah = 10) @ yay() : bummer(); // Yay, blah is: 10 | |
blah = 5; | |
foo() @ (blah = 10) @ foo() @ yay() : bummer(); // Bummer, blah is: 10 | |
blah = 5; | |
foo() @ ((blah = 10) @ yay() : bummer()) : bummer(); // Yay, blah is: 10 | |
blah = 10; | |
foo() @ ((blah = 5) @ yay() : bummer()) : bummer(); // Bummer, blah is: 10 | |
blah = 5; | |
foo() @ yay() : ((blah = 5) @ foo() @ yay() : bummer()) // Yay, blah is: 5 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function foo() { | |
var p = promise; // `promise` being new auto keyword kinda like `arguments` | |
setTimeout(function(){ | |
if (blah == 5) { | |
p.fulfill("`blah` was the correct value!"); | |
} | |
else { | |
p.fail(2, "`blah` was an incorrect value.", blah); | |
} | |
},1000); | |
p.defer(); // flag this function as needing to defer its promise | |
} | |
function yay() { | |
console.log(promise.messages[0]); | |
} | |
function bummer() { | |
console.log(promise.messages[0]+": "+promise.messages[1]); | |
} | |
var blah = 5; | |
foo() @ yay() : bummer(); // `blah` was the correct value! | |
(blah = 10) @ foo() @ yay() : bummer(); // 2: `blah` was an incorrect value. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function onclick(obj,callback) { | |
obj.addEventHandler("click", callback, true); | |
} | |
var clicker = document.getElementById("clicker"), | |
btn = document.getElementById("btn") | |
; | |
onclick(elem,function(){ | |
onclick(btn,function(){ | |
console.log("clicker & button clicked"); | |
}); | |
}); | |
-------------------------- | |
function onclick(obj) { | |
var p = promise; | |
obj.addEventHandler("click", function(){ | |
p.fulfill(); | |
}, true); | |
p.defer(); | |
} | |
var clicker = document.getElementById("clicker"), | |
btn = document.getElementById("btn") | |
; | |
onclick(elem) @ | |
onclick(btn) @ | |
function(){ console.log("clicker & button clicked"); }; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function requestA() { | |
var p = promise; | |
// make some request remotely | |
// when it finishes, call p.fulfill(requestA_value); | |
// if it errors, call p.fail(); | |
p.defer(); // signal this function as deferred completion | |
} | |
function requestB() { | |
var p = promise; | |
// make some request remotely | |
// when it finishes, call p.fulfill(requestA_value); | |
// if it errors, call p.fail(); | |
p.defer(); // signal this function as deferred completion | |
} | |
-------------------------- | |
// intersection (serial): | |
function intersection(A, B) { | |
function get_result() { return promise.messages[0]; } | |
var p = promise, resultA, resultB, result; | |
A() @ (resultA = get_result()) @ B() @ (resultB = get_result()) @ (function(){ | |
result = resultA + resultB; | |
console.log(result); | |
p.fulfill(result); | |
}); | |
p.defer(); | |
} | |
intersection(requestA, requestB) @ function(){ | |
console.log("Intersection: "+promise.messages[0]); | |
}; | |
-------------------------- | |
// intersection (parallel): | |
function intersection(A, B) { | |
function complete() { | |
var p = promise, result; | |
results.push(p.messages[0]); | |
if (results.length == 2) { | |
result = results[0] + results[1]; | |
console.log(result); | |
p.fulfill(result); | |
} | |
} | |
var p = promise, | |
results = array() | |
; | |
// here, A() and B() will run in parallel, and `complete` will be the gate | |
A() @ complete(); | |
B() @ complete(); | |
p.defer(); | |
} | |
intersection(requestA, requestB) @ function(){ | |
console.log("Intersection: "+promise.messages[0]); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// the following is meant to explore a possible shim implementation | |
// (similar to how coffeescript compiles to uglier js) that could | |
// emulate the proposed @ behavior in current JS. | |
X @ Y | |
when(X, function() { Y; }); | |
// but what if Y relies on the `this` or `arguments`? | |
function do_Y() { | |
Y; | |
} | |
var args = Array.prototype.slice.call(arguments); | |
args.unshift(this); | |
var bound_Y = do_Y.prototype.bind.apply(this,args); | |
when(X, bound_Y); | |
// what about if Y uses a `return`? Too complicated. I'd | |
// simply say the semantics of @ expressions are that the | |
// `rvalue` cannot be a `return` statement/expression. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Because @ can suspend the containing statement, it can be used in control and loop structures, as well | |
if (X() @ true) { | |
// if X() completes immediately, this block will run | |
// if X() defers, the whole if-statement is paused. | |
// if X() eventually fulfills successfully, the @ expression | |
// will evaluate the `true`, and this block will then run | |
// if X() eventually fails, the @ expression will evaluate to | |
// `undefined` (falsy), and this block will NOT run | |
} | |
// OR, more explicitly if you like | |
if (X() @ true : false) { | |
// will be have the exact same way | |
} | |
// in a loop: | |
for (i=0; X() @ true; i++) { | |
// loop iteration each time that X() fulfills successfully, | |
// whether that's immediate or later | |
// stop when X() fails | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here's the follow-up blog post continuing the discussion: http://blog.getify.com/2010/12/native-javascript-sync-async/
And here's the discussion thread on "es-discuss" list: https://mail.mozilla.org/pipermail/es-discuss/2010-December/012278.html