Promise - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: General (https://python-forum.io/forum-1.html) +--- Forum: Code sharing (https://python-forum.io/forum-5.html) +--- Thread: Promise (/thread-4434.html) |
Promise - nilamo - Aug-16-2017 Earlier today, I had the opportunity (lol) to write a javascript fallback function for Promises for browsers which don't support Promises. Which... is pretty much only IE (edge supports them just fine). For reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise The basic idea, is that you create a promise and immediately return it, so the caller can attach callbacks which will be called when the Promise is fulfilled (...when the callee fullfills it's Promise that it'll eventually finish). The use case for javascript is to make code look less disgusting when working with ajax calls. For example, let's say you had a function get(url) , which took, as a string argument, the url to request, and returned a Promise. You could then write the following:get("/some_content.json") .then(function(data) { alert(data); }); console.log("this will happen before the ajax callback");It's basically just syntactic sugar, and is mostly useless now that javascript supports async/await, but that's beside the point. The point is that it's actually pretty neat, and one of the cool parts is that you can chain data from one promise to the next, to create semi-functional programs. And it got me to wondering how hard it would be to writing the same functionality in Python. And it turns out... not very difficult at all. class STATES: Pending = 0 Resolved = 1 Rejected = 2 class Promise: def __init__(self, worker): keys = [STATES.Resolved, STATES.Rejected] self.state = STATES.Pending self.callbacks = {key: [ ] for key in keys} self.content = {key: None for key in keys} def callback_generator(state): def callback(response=None): self.content[state] = response self.state = state self.__run(state) return callback worker( callback_generator(STATES.Resolved), callback_generator(STATES.Rejected) ) def __run(self, key): # a callback is only ever called a single time while self.callbacks[key]: callback = self.callbacks[key].pop(0) # chain the output of one callback into the input of the next response = callback(self.content[key]) if response is not None: self.content[key] = response def __chain_method(self, state, callback): self.callbacks[state].append(callback) # register the new promise's callbacks with the parent, so data/errors # will chain def worker(resolve, fail): self.callbacks[STATES.Resolved].append(resolve) self.callbacks[STATES.Rejected].append(fail) future = Promise(worker) future.content = self.content future.state = self.state if self.state == state: self.__run(state) return future def then(self, callback): return self.__chain_method(STATES.Resolved, callback) def catch(self, callback): return self.__chain_method(STATES.Rejected, callback) if __name__ == "__main__": def callback(resolve, fail): print("before resolve") resolve(5) print("after resolve") # fail(Exception("boop")) future = Promise(callback) future.then(lambda x: x**2).then(print) #.catch(lambda err: print(f"Uh oh: {err}")) RE: Promise - nilamo - Aug-17-2017 This all seems kinda crazy, so here's some more info. Almost all code would be consumers of a promise, only a library would implement one. The important part is that the code generating a promise should have a little bit of setup, but otherwise immediately return, and use the callbacks to signal when it's done doing it's work. Here's a sample use case, using a delayed timer with a callback, that gets called after that delay: from promise import Promise import threading import time def delayed(delay): def runner(resolved, failed): def thread(): time.sleep(delay) resolved() threading.Thread(target=thread).start() return Promise(runner) if __name__ == "__main__": def callback(*args): print("Inside callback") print("Before delayed callback") delayed(2.5).then(callback) print("After delayed callback") for _ in range(5): print("waiting...") time.sleep(1)
RE: Promise - nilamo - Aug-18-2017 Wait wait wait. It makes more sense for the Promise itself to handle threading, since almost anything that uses it would involve threading anyway. import threading class STATES: Pending = 0 Resolved = 1 Rejected = 2 class Promise: def __init__(self, worker): keys = [STATES.Resolved, STATES.Rejected] self.state = STATES.Pending self.callbacks = {key: [] for key in keys} self.content = {key: None for key in keys} def callback_generator(state): def callback(response=None): self.content[state] = response self.state = state self.__run(state) return callback self._lock = threading.Lock() self._thread = threading.Thread(target=worker, args=( callback_generator(STATES.Resolved), callback_generator(STATES.Rejected))) self._thread.start() def join(self): with self._lock: self._thread.join() def __run(self, key): # a callback is only ever called a single time with self._lock: while self.callbacks[key]: callback = self.callbacks[key].pop(0) # chain the output of one callback into the input of the next response = callback(self.content[key]) if response is not None: self.content[key] = response def __chain_method(self, state, callback): with self._lock: self.callbacks[state].append(callback) # register the new promise's callbacks with the parent, so data/errors # will chain def worker(resolve, fail): self.callbacks[STATES.Resolved].append(resolve) self.callbacks[STATES.Rejected].append(fail) future = Promise(worker) future.content = self.content future.state = self.state if self.state == state: self.__run(state) return future def then(self, callback): return self.__chain_method(STATES.Resolved, callback) def catch(self, callback): return self.__chain_method(STATES.Rejected, callback) import time def delayed(delay): def runner(resolved, failed): time.sleep(delay) resolved() return Promise(runner) if __name__ == "__main__": def callback(*args): print("Inside callback") print("Before delayed callback") future = delayed(2.5).then(callback) print("After delayed callback") for _ in range(5): print("waiting...") time.sleep(1) future.join()
|