Last active
April 24, 2024 19:43
-
-
Save tinkerer-red/49bb859723afdd5c3c89fe836009f7cc to your computer and use it in GitHub Desktop.
Javescript Promises in GameMaker's GML
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
#macro PROMISE_MAX_TIME (1/60 * 1_000 * 1_000) * (1/16) //the max time in milli seconds to spend on the promises, default is 1/16 of frame time of a 60 fps game | |
enum PROMISE_STATE { | |
PENDING, | |
RESOLVED, | |
REJECTED, | |
PAUSED, | |
}; | |
function __PromiseNamespace__() { | |
//used to help evacuate the global struct so the variables are not easily accessed by users | |
static __global = { | |
promises_time_source : time_source_create(time_source_game, 1, time_source_units_frames, function() { | |
static __global = __PromiseNamespace__() | |
//handle all of the currently active processes | |
var _time_to_live = get_timer() + PROMISE_MAX_TIME; | |
var _i=0; repeat(array_length(__global.active_promises)) { | |
var _promise = __global.active_promises[_i]; | |
_promise.Execute(_time_to_live); | |
if (_promise.state == PROMISE_STATE.RESOLVED) | |
|| (_promise.state == PROMISE_STATE.REJECTED) { | |
array_delete(__global.active_promises, _i, 1); | |
_i-=1; | |
} | |
//early out | |
if (get_timer() >= _time_to_live) { | |
break; | |
} | |
_i+=1;}//end repeat loop | |
//if we have completed all promises | |
if (array_length(__global.active_promises) == 0) { | |
time_source_pause(__global.promises_time_source) | |
} | |
}, [], -1), | |
active_promises : [], | |
}; | |
return __global; | |
} | |
function Promise(_resolve_callback=undefined, _reject_callback=function(_reason){ show_debug_message("Promise Failed with error : "+_reason) }) constructor { | |
// State and value of the promise | |
executors = []; | |
state = PROMISE_STATE.PENDING; | |
value = undefined; | |
reason = undefined; | |
on_resolved = _resolve_callback; | |
on_rejected = _reject_callback; | |
static Then = function(_executor) { | |
var _this_promise = (is_instanceof(self, Promise)) ? self : new Promise(); | |
array_push(_this_promise.executors, _executor); | |
return _this_promise; | |
}; | |
static Catch = function(_rejected_callback) { | |
var _this_promise = (is_instanceof(self, Promise)) ? self : new Promise(); | |
_this_promise.on_rejected = _rejected_callback; | |
return _this_promise; | |
}; | |
static Finally = function(_resolved_callback) { | |
var _this_promise = (is_instanceof(self, Promise)) ? self : new Promise(); | |
_this_promise.on_resolved = _resolved_callback; | |
return _this_promise; | |
}; | |
static Resolve = function(_value) { | |
var _this_promise = (is_instanceof(self, Promise)) ? self : new Promise(); | |
_this_promise.state = PROMISE_STATE.PENDING; | |
_this_promise.value = _value; | |
return _this_promise; | |
}; | |
static Reject = function(_reason) { | |
var _this_promise = (is_instanceof(self, Promise)) ? self : new Promise(); | |
_this_promise.state = PROMISE_STATE.REJECTED; | |
_this_promise.reason = _reason; | |
return _this_promise; | |
}; | |
#region Promise Parenting | |
static All = function(_arr_of_promises) { | |
var _parent_promise = new Promise(); | |
_parent_promise.state = PROMISE_STATE.PAUSED; | |
_parent_promise.sub_promises = _arr_of_promises; | |
_parent_promise.resolved_count = 0; | |
_parent_promise.value = []; | |
var _i=0; repeat(array_length(_arr_of_promises)) { | |
var _promise = _arr_of_promises[_i]; | |
_promise.parent = _parent_promise; | |
_promise.index_in_parent = _i; | |
_promise.Then(method(_promise, function(_value){ | |
parent.value[index_in_parent] = _value; | |
parent.resolved_count += 1; | |
if (parent.resolved_count == array_length(parent.sub_promises)) { | |
parent.state = PROMISE_STATE.PENDING; | |
} | |
return _value; | |
})) | |
.Catch(method(_parent_promise, function(_reason) { | |
if (state != PROMISE_STATE.REJECTED) { | |
state = PROMISE_STATE.REJECTED; | |
reason = _reason; | |
} | |
})); | |
_i+=1;}//end repeat loop | |
return _parent_promise; | |
}; | |
//wait for all to finish, then get reports | |
static AllSettled = function(_arr_of_promises) { | |
var _parent_promise = new Promise(); | |
_parent_promise.state = PROMISE_STATE.PAUSED; | |
_parent_promise.sub_promises = _arr_of_promises; | |
_parent_promise.resolved_count = 0; | |
_parent_promise.value = []; | |
var _i=0; repeat(array_length(_arr_of_promises)) { | |
var _promise = _arr_of_promises[_i]; | |
_promise.parent = _parent_promise; | |
_promise.index_in_parent = _i; | |
_promise.Then(method(_promise, function(_value){ | |
parent.value[index_in_parent] = { status: "RESOLVED", value: _value }; | |
parent.resolved_count += 1; | |
if (parent.resolved_count == array_length(parent.sub_promises)) { | |
parent.state = PROMISE_STATE.PENDING; | |
} | |
return _value; | |
})) | |
.Catch(method(_promise, function(_reason) { | |
var _error = (is_struct(_reason)) ? _reason.message : _reason; | |
parent.value[index_in_parent] = { status: "rejected", reason: _error }; | |
parent.resolved_count += 1; | |
if (parent.resolved_count == array_length(parent.sub_promises)) { | |
parent.state = PROMISE_STATE.PENDING; | |
} | |
})); | |
_i+=1;}//end repeat loop | |
return _parent_promise; | |
}; | |
//if any succeed | |
static Any = function(_arr_of_promises) { | |
var _parent_promise = new Promise(); | |
_parent_promise.state = PROMISE_STATE.PAUSED; | |
_parent_promise.sub_promises = _arr_of_promises; | |
_parent_promise.resolved_count = 0; | |
_parent_promise.value = []; | |
var _i=0; repeat(array_length(_arr_of_promises)) { | |
var _promise = _arr_of_promises[_i]; | |
_promise.parent = _parent_promise; | |
_promise.index_in_parent = _i; | |
_promise.Then(method(_parent_promise, function(_value) { | |
state = PROMISE_STATE.PENDING; | |
value = _value; | |
return _value; | |
})) | |
.Catch(method(_parent_promise, function(_reason){ | |
resolved_count += 1; | |
if (resolved_count == array_length(sub_promises)) { | |
state = PROMISE_STATE.REJECTED; | |
reason = _reason; | |
} | |
})); | |
_i+=1;}//end repeat loop | |
return _parent_promise; | |
}; | |
//first come first server | |
static Race = function(_arr_of_promises) { | |
var _parent_promise = new Promise(); | |
_parent_promise.state = PROMISE_STATE.PAUSED; | |
_parent_promise.sub_promises = _arr_of_promises; | |
_parent_promise.resolved_count = 0; | |
_parent_promise.value = []; | |
var _i=0; repeat(array_length(_arr_of_promises)) { | |
var _promise = _arr_of_promises[_i]; | |
_promise.parent = _parent_promise; | |
_promise.index_in_parent = _i; | |
_promise.Then(method(_parent_promise, function(_value) { | |
if (state == PROMISE_STATE.PAUSED) { | |
state = PROMISE_STATE.RESOLVED; | |
value = _value; | |
} | |
return _value; | |
})) | |
.Catch(method(_parent_promise, function(_reason) { | |
if (state == PROMISE_STATE.PAUSED) { | |
state = PROMISE_STATE.REJECTED; | |
reason = _reason; | |
} | |
})); | |
_i+=1;}//end repeat loop | |
return _parent_promise; | |
}; | |
#endregion | |
//note providing no start time will force the promise to execute all sub processes | |
static Execute = function(_time_to_live=infinity) { | |
if (state == PROMISE_STATE.PAUSED) { | |
return true; | |
} | |
if (state == PROMISE_STATE.PENDING) { | |
if (array_length(executors)) { | |
try { | |
var _j=0; repeat(array_length(executors)) { | |
var _executor = executors[0]; | |
var _val = _executor(value); | |
if (!is_undefined(_val)) { | |
array_delete(executors, 0, 1); | |
value = (is_undefined(_val)) ? value : _val; | |
} | |
var _length = array_length(executors); | |
if (_length == 0) { | |
state = PROMISE_STATE.RESOLVED; | |
} | |
//if (_length == 1) | |
//&& (on_resolved == undefined) { | |
// state = PROMISE_STATE.RESOLVED; | |
//} | |
//early out | |
if (get_timer() >= _time_to_live) { | |
break; | |
} | |
_j+=1;}//end repeat loop | |
} | |
catch (_error) { | |
state = PROMISE_STATE.REJECTED; | |
reason = _error; | |
} | |
} | |
else { | |
state = PROMISE_STATE.RESOLVED; | |
} | |
} | |
if (state == PROMISE_STATE.RESOLVED) { | |
if (on_resolved != undefined) { | |
on_resolved(value); | |
} | |
} | |
if (state == PROMISE_STATE.REJECTED) { | |
if (on_rejected != undefined) { | |
var _error = (is_struct(reason)) ? reason.message : reason; | |
on_rejected(_error); | |
} | |
} | |
return true; | |
} | |
var _promise = self; | |
#region Private | |
static __promise_id = 0; | |
__promise_id+=1; | |
_promise.promise_id = __promise_id; | |
static __global = __PromiseNamespace__(); | |
time_source_start(__global.promises_time_source); | |
array_push(__global.active_promises, _promise); | |
#endregion | |
return _promise; | |
} | |
//init statics | |
new Promise(); | |
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
#region Promise | |
//Promise | |
new Promise(function(_value){ | |
show_debug_message("Promise Working correctly\n") | |
}, | |
function(_error){ | |
show_debug_message("Promise Failed with error :: "+_error) | |
}) | |
/// | |
/// NOTE: Returning undefined inside .Then() will postpone the progression of the promise. | |
/// | |
Promise.Then(function(_value){ | |
show_debug_message("Promise.Then Working correctly\n") | |
return 10; | |
}) | |
Promise.Resolve("Resolving") | |
.Finally(function(_value){ | |
show_debug_message("Promise.Resolve Working correctly\n") | |
}) | |
//Promise with then | |
Promise.Reject("Rejection intentional") | |
.Catch(function(_reason){ | |
show_debug_message("Promise.Reject Working correctly\n") | |
}) | |
//Promise with finally | |
Promise.Resolve(10) | |
.Finally(function(_value){ | |
show_debug_message(_value); | |
show_debug_message("Promise.Finally Working correctly\n") | |
}) | |
.Catch(function(_reason) { | |
show_debug_message("Promise.Then :: FAILED\n"); | |
}) | |
//Promise with catch | |
Promise.Then(function(_value){ | |
throw "intentionally throwing error to test .catch" | |
return _value; | |
}) | |
.Finally(function(_value) { | |
show_debug_message("Promise.Then :: FAILED\n"); | |
}) | |
.Catch(function(_reason){ | |
show_debug_message("Promise failed with reason: " + _reason); | |
show_debug_message("Promise.Catch Working correctly\n") | |
}) | |
#endregion | |
#region Promise.All | |
//success | |
var p1 = Promise.Resolve(1); | |
var p2 = Promise.Resolve(2); | |
Promise.All([p1, p2]) | |
.Then(function(_value) { | |
show_debug_message("All Promises resolved with values: " + string(_value)); | |
show_debug_message("Promise.All.Then Working correctly\n"); | |
return _value; | |
}) | |
.Finally(function(_value) { | |
show_debug_message("All Promises resolved with values: " + string(_value)); | |
show_debug_message("Promise.All.Finally Working correctly\n") | |
}) | |
.Catch(function(_reason) { | |
show_debug_message("Promise.All failed with reason: " + _reason); | |
show_debug_message("Promise.All.Finally :: FAILED\n"); | |
}); | |
//failure | |
var p1 = Promise.Resolve(3); | |
var p2 = Promise.Reject("error!"); | |
Promise.All([p1, p2]) | |
.Then(function(_values) { | |
show_debug_message("All Promises resolved with values: " + string(_values)); | |
}) | |
.Finally(function(_value) { | |
show_debug_message("Promise.All.Catch :: FAILED\n"); | |
}) | |
.Catch(function(_reason) { | |
show_debug_message("Promise.All failed with reason: " + _reason); | |
show_debug_message("Promise.All.Catch Working correctly\n") | |
}); | |
#endregion | |
#region Promise.AllSettled | |
var p1 = Promise.Resolve(10); | |
var p2 = Promise.Reject("error"); | |
Promise.AllSettled([p1, p2]) | |
.Then(function(_results) { | |
show_debug_message("All Promises settled with results: " + string(_results)); | |
show_debug_message("Promise.AllSettled.Then Working correctly\n"); | |
return _results; | |
}) | |
.Finally(function(_results) { | |
show_debug_message("All Promises settled with results: " + string(_results)); | |
show_debug_message("Promise.AllSettled.Finally Working correctly\n") | |
}) | |
.Catch(function(_reason) { | |
show_debug_message("Promise.AllSettled.Finally :: FAILED\n"); | |
}); | |
//Expected Output: "All Promises settled with results: [{status: 'fulfilled', value: 10}, {status: 'rejected', reason: 'error'}]" | |
/// | |
/// NOTE: it is not possible to reach a catch statement when using Promise.AllSettled | |
/// | |
#endregion | |
#region Promise.Any | |
//resolve | |
var p1 = Promise.Reject("error1"); | |
var p2 = Promise.Resolve(20); | |
Promise.Any([p1, p2]) | |
.Then(function(_value) { | |
show_debug_message("At least one Promise resolved with value: " + string(_value)); | |
show_debug_message("Promise.Any.Then Working correctly\n"); | |
return _value; | |
}) | |
.Finally(function(_value) { | |
show_debug_message("At least one Promise resolved with value: " + string(_value)); | |
show_debug_message("Promise.Any.Finally Working correctly\n"); | |
}) | |
.Catch(function(_reason) { | |
show_debug_message("Promise.Any.Finally :: FAILED\n"); | |
}); | |
//reject | |
var p1 = Promise.Reject("error1"); | |
var p2 = Promise.Reject("error2"); | |
Promise.Any([p1, p2]) | |
.Then(function(_value) { | |
show_debug_message("At least one Promise resolved with value: " + string(_value)); | |
return _value; | |
}) | |
.Finally(function(_value) { | |
show_debug_message("Promise.Any.Catch :: FAILED\n"); | |
}) | |
.Catch(function(_reason) { | |
show_debug_message("All Promises failed: " + _reason); | |
show_debug_message("Promise.Any.Catch Working correctly\n") | |
}); | |
#endregion | |
#region Promise.Race | |
//resolve | |
var p1 = Promise.Resolve(10); | |
var p2 = Promise.Reject("error"); | |
Promise.Race([p1, p2]) | |
.Then(function(value) { | |
show_debug_message("First resolved Promise value: " + string(value)); | |
show_debug_message("Promise.Race.Then Working correctly\n") | |
}) | |
.Finally(function(value) { | |
show_debug_message("First resolved Promise value: " + string(value)); | |
show_debug_message("Promise.Race.Finally Working correctly\n") | |
}) | |
//reject | |
var p1 = Promise.Reject("error"); | |
var p2 = Promise.Resolve(20); | |
Promise.Race([p1, p2]) | |
.Then(function(value) { | |
show_debug_message("First resolved Promise value: " + string(value)); | |
}) | |
.Catch(function(reason) { | |
show_debug_message("First settled Promise was rejected: " + reason); | |
show_debug_message("Promise.Race.Catch Working correctly\n") | |
}); | |
#endregion | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment