You've happened on the first try to encounter a limitation of GlowScript, namely that it doesn't work to place a rate or waitfor or sleep statement in a function that is driven by a widget. Happily, I think I now glimpse a way to give an informative error message about this, and I also glimpse a way to document the problem in such a way that users would notice.
Interestingly, had you tried the same program in VPython 7 it would have worked, after dealing with an error message for the button statement: NameError: name 'launch' is not defined. The error comes from the need in Python to define a function before calling it. Move the button statement to the end of the program and it works.
The GlowScript problem is very deep and is related to the fact that JavaScript code cannot be interrupted. For example, an infinite loop in JavaScript (to which Python is compiled using the RapydScript-NG transpiler) locks up the browser. In order for infinite loops to be possible, I have to preprocess your code in a way that includes invoking the Streamline module that rewrites your "synchronous" code into "asynchronous" code, keying off the presence of rate or waitfor or sleep. For highly technical reasons, calling your launch function explicitly can be handled by Streamline, but not calling launch as a function bound to a widget. (Similarly, there's a lot of preprocessing of GlowScript VPython to allow you to use "operator overloading" such as vector+vector being treated differently from scalar+scalar; JavaScript does not natively support operator overloading.)
As you suppose, a natural way to do what you want is this (which works in GlowScript VPython and VPython 7):