Skip to content

A Godot addon that provides functionality to control and sync coroutines with Promises similar to JavaScript. Is functional signals, functions, other Promises, and all other types.

License

Notifications You must be signed in to change notification settings

SoulsTogetherX/GodotPromise

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GodotPromise

Hello everyone in the future!

I've noticed the current Promise types on the Godot Asset Library are lacking in a few ways, so I improved on them.

Features

I replicated ALL functions related to the Promise type in Javascript (except try(), but that's because its behavior is implied by default).

Promises can be accepted or rejected. then(), catch(), and finally() functions work as expected.

This type works for any asynchronous type (Signals, Callables, other Promises), as well as synchronous types.

The function to_string() converts the Promise into the format "Promise<Type>".

It is possible and recommended to chain Promises.

It is fully documented, and the code is efficient and compact to help with easy understanding.

I made it easy to create custom Promise functionality with the use of modular Inner Classes.

Can be mindlessly plugged in to synchronize coroutines or be easily extended to allow anything else you may need by extending from the Inner Classes at the bottom of the document.

Known Issues

None

CODE EXAMPLES

To use, simply create and await on a Promise type as such:

await Promise.new(val).finished

You may also nest Promises.

await Promise.new(Promise.new(val)).finished

You can delay the execution of promises and activate them on command.

var p := Promise.new(Promise.new(val), false)
p.execute()
await p.finished

You may also use one of the premade static options already available. A few examples:

await Promise.any([val1, val2, val3, val4]).finished
await Promise.race([val1, val2, val3, val4]).finished
await Promise.allSettled([val1, val2, val3, val4]).finished
await Promise.all([val1, val2, val3, val4]).finished

You may directly cause a rejection or a resolution.

await Promise.reject(val).finished
await Promise.reject_raw(val).finished
await Promise.resolve(val).finished
await Promise.resolve_raw(val).finished

You may also chain Promises.

print(
	await Promise.any([val1, val2, val3, val4]).then(
		"At least one coroutine was resolved"
	).then(
		"A coroutine was accepted! :O"
	).catch(
		"All coroutines were rejected! O:"
	).finally(
		"I always run!!! :D"
	).finished
)

You may also pipeline the result of Promises.

func _pipe_test_funcs(arg : int) -> int:
	return arg * 2

print(
	Promise.new(1).then(_pipe_test_funcs, true).then(_pipe_test_funcs, true).then(_pipe_test_funcs, true)
) # <- Returns 8

Common mistakes

Note that Promise is a complex object, so it's easy to misuse.

You'd expect the code below to work, for instance...

func test() -> Signal:
	return Promise.new().finished

...but this actually causes an error.

Promise is a RefCounter object. This means that, in a situation where the reference to a Promise is no longer stored anywhere, the Promise will automatically clear itself.

To fix this, you must store the Promise somehow...

var p : Promise
func test() -> Signal:
	p = Promise.new()
	return p.finished

...or return the Promise itself...

func test() -> Promise:
	return Promise.new()

func main() -> void:
	await test().finished

It is also to easy to mess up how to delay Promises.

Look at the code below...

func test() -> void:
	p = Promise.new()
	for n in 10:
		p.then(get_tree().create_timer(0.1).timeout)
	await p.finished

At first glance, it appears that this function will await for exactly 0.1 * 10 seconds. However, no. It waits for exactly 0.1 seconds.

This is because you are creating all get_tree().create_timer(0.1) in the same frame. These timers will all finish 0.1 seconds later, regardless of what happens, and the Promises respect that.

Instead, you need to create and await the timers on demand. For example...

func test_helper() -> void:
	await get_tree().create_timer(0.1).timeout

func test() -> void:
	p = Promise.new()
	for n in 10:
		p.then(test_helper)
	await p.finished

This will work and await for exactly 0.1 * 10 seconds, as the timers are being created and awaited on demand.

It's also easy to confuse Callables with return values.

In Promise.new(print("Hello")), we are promising the return value of print, after it’s execution, which is interpreted as null.

Meanwhile, in Promise.new(print.bind("Hello")), we are promising the Callable print, which will be called when the Promise is requested to be resolved.

For more information, the documentation includes a full list of functions and utility. And, as stated, the MAIN purpose of this framework is to allow user customizability. No matter your requirement, it will be easy to code a custom Promise protocol to handle it when using this framework.

Enjoy.

About

A Godot addon that provides functionality to control and sync coroutines with Promises similar to JavaScript. Is functional signals, functions, other Promises, and all other types.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published