Callbacks, futures and promises
Callbacks are functions that are passed as arguments to other functions or functions that are called inside other functions. These functions can be invoked when an event occurs. Callbacks are usually used as a join point for multithreading/multiprocessing solutions. The following example shows a toy example of a callback function that is invoked from each thread, after the thread has done some processing:
def worker(num, callback):
print(f"Worker {num} starting...")
time.sleep(num) # Simulate some work
print(f"Worker {num} finished.")
callback(num) # Call the callback
def callback_function(num):
print(f"Callback for worker {num} called.")
if __name__ == "__main__":
threads = []
for i in range(5):
thread = threading.Thread(target=worker, args=(i, callback_function))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
Multiprocessing and multithreading callbacks are treated in detail in Anchor 3. While very popular some time ago in other languages, such as JavaScript, this mechanism has some drawbacks that need to be mitigated, including nesting of callbacks, difficult debugging and race conditions. Other mechanisms have been developed to address these and other difficulties, like futures and promises.
The result of an asynchronous call is unknown at the start of the main thread’s execution, and the future/promise concept allows programmers to wait until something is returned before continuing a process. Futures/promises can be awaited up until their execution finishes and can execute a callback function when they end.
Semantics for futures/promises vary by programming language. In Python, futures are part of the standard language API, but promises are implemented by the community based on the Promises/A+ (https://promisesaplus.com/implementations) specification. Futures and promises are covered in detail in Anchor 2.