What Are Coroutines? - Promises in PHP Survey Results
Hello again! As promised, I am back with this post to share with you the results of my survey about Promises in PHP, as described in my previous post. If you want to download a copy of the results, scroll to the bottom of this post where I have a report attached.
First off, I’d like to thank everyone who took the survey! (You know who you are.) As of this writing, 62 people took the survey, which was three times the number of people I thought would take the survey. I think it gives a little insight in what knowledge developers out there have on promises and coroutines, and what you all think of them. I am especially grateful for those who added additional thoughts in the additional comments section at the end; I sat down and read every single one.
Secondly, I’ve decided to not close the survey. I had announced that the survey would be closed after this post is published, but I decided that since the results are public and real-time, more people might wish to take the survey and we would be able to see how opinions change (or don’t). So if you haven’t taken it already, you still can.
Current results
Now let’s discuss the results. The first question was pretty straightforward and helped establish context for the rest of the survey:
It looks like about 85% of you have heard of, and possibly used, promises in some form or another. I would probably attribute this to the success of promises in JavaScript land and its popularity over there. Next question:
This was kind of an open-ended, opinion-based question. Those who took the survey seemed to be split in half over promises, with 53% thinking they are a good idea, and the other half not so sure. There were about 16% who repeated that they hadn’t heard of promises, which makes sense. A few of you didn’t like the idea in PHP, but more were indifferent. I think that a neutral position is perfectly valid, and I am glad you took the survey anyway to hear your opinion.
A few of you pointed out in this answer that promises should only be used if implemented properly in an asynchronous environment, which is an excellent point. Even if promises (or async in general) become more common in PHP, I would be appalled if everyone started using promises even in synchronous code, just to jump on the bandwagon.
Coroutines
And now the question I’d like to address the most in this post: coroutines.
A significant portion of you had never heard of “coroutines”. Now this doesn’t surprise me, since they are not even close to mainstream in PHP, and they aren’t incredibly mainstream outside PHP to my knowledge either. I’d recommend you read Nikita Popov’s article about coroutines, though it is by no means a gentle introduction, which I have yet to find a good one.
Coroutines not only make writing asynchronous code simpler to read and write (in my opinion), but it also gives back the power of exception handling to your code. Last post, I presented the following example code using promises to simplify messy callbacks:
$asyncFS = new CoolAsyncFileSystemImpl();
$asyncFS->fileGetContents('query.sql')
->then(function($contents) {
return AsyncDB::query($contents);
})->then(function($result) {
return $asyncFS->fileGetContents('http://example.com?x='.$result['x']);
})->then(function($response) {
echo $response;
});
Firstly, I forgot error handling, so let’s address that:
$asyncFS = new CoolAsyncFileSystemImpl();
$asyncFS->fileGetContents('query.sql')
->then(function($contents) {
return AsyncDB::query($contents);
})->then(function($result) {
return $asyncFS->fileGetContents('http://example.com?x='.$result['x']);
})->then(function($response) {
echo $response;
}, function(Exception $exception) {
echo "Failed to do the cool thing I wanted to! Reason: ".$exception->getMessage();
return $exception;
});
Any error that causes a promise to be rejected will be passed along to the last promise, so we add an onRejected
callback to the end to figure out what went wrong. The downfall of promises is if you forget that last callback, the failed promise will be ignored, and any thrown exceptions will disappear into the void and you will be unaware if a major problem was encountered. Let’s rewrite this again to take advantage of the awesomeness of coroutines:
coroutine(function() {
$asyncFS = new CoolAsyncFileSystemImpl();
try {
$contents = (yield $asyncFS->fileGetContents('query.sql'));
$result = (yield AsyncDB::query($contents));
$response = (yield $asyncFS->fileGetContents('http://example.com?x='.$result['x']));
} catch (Exception $exception) {
echo "Failed to do the cool thing I wanted to! Reason: ".$exception->getMessage();
}
})();
Wrapped in a non-specific and theoretical coroutine()
function, we can write code that is much easier for our linear brains to wrap our heads around. In addition, If we took the try/catch block out, any error thrown will bubble up and will result in an uncaught exception, which is exactly how error handling usually works.
I demonstrated some similar (though shorter) code in the survey and asked your opinions about them:
Ratings weren’t bad overall, but it was definitely clear that not everyone was happy with how simple either method looked. To be honest, I think async and await keywords may tip the scales toward coroutines a bit more, but there may also be a third option that hasn’t been suggested or even invented yet that is a better option. I’d like to find that option, but for now I think generators are the best we have so far.
Now the last question I got a few comments on:
One person commented that promises should be “implemented properly”, and I’d like to determine exactly what that is. Should PHP promises follow the Promises/A+ specification, which is targeted toward JavaScript? Should there be a PSR package for a PromiseInterface
? Some did not like that idea, but it would solve incompatibility issues between implementations, like the gap between ReactPHP and Guzzle promises. Issues like these have yet to be decided on.
Conclusion
While the results of the survey can be concluded, I think the search for the best way to design asynchronous PHP code will not conclude for some time yet. Whether we use promises, coroutines, or some other kind of future API, we should definitely be investing in asynchronous PHP. I think that event-based code that is easily adapted to run on multiple threads, processes, or even computers is part of the future of computing as a whole, and each time we invent ways to enable our code to run more concurrently, we contribute to the effort to move toward that future.
If you’d like to save the results of the survey for your own records, you can download a PDF version here.
As always, feel free to let me know what you think in the comments below, or reach out to me on Twitter.
12 comments
Let me know what you think in the comments below. Remember to keep it civil!
Subscribe to this thread
You do know that this is not asynchronous code, right? There’s only a
single task running, all the time, and it voluntarily gives control back
to the scheduler so another task can run. It’s very basic time-sharing,
on top of the single-threaded PHP process.
Well promises themselves are not asynchronous, and neither are coroutines, that’s true. But coupled with an event loop and non-blocking I/O (which this article doesn’t discuss for simplicity), you can indeed create asynchronous code. Only one PHP thread required. See http://reactphp.org for an example of an asynchronous, single-threaded library.
It of course is voluntary, but that is what can help make it efficient. Coding in this way is like, “I’m going to do an expensive I/O operation now, so I’ll yield my processing time to someone else until that operation is done.” It makes the most of the CPU time available in a thread.
You’re confusing people by making them believe that cooperative multitasking is asynchronous code. It is, and will always be, a single-threaded process delegating resources to tasks, which voluntarily give control back. That’s synchronous. Which is a terrible idea, btw. That’s why preemptive multitasking was created.
I’m not sure if you understand exactly how generators work in PHP. Read Nikita’s article again and implement a simple routine to read data from a database or make an expensive HTTP call. You’re only giving control back AFTER the expensive task was completed. You can’t continue processing, it’s not really asynchronous.
You can, however, work more efficiently in chunks. Fetch a chunk of data, process, give back control, then continue later on. I’m pretty sure you’re confusing what async/sync really means here.
I think you misunderstand me. I’m not saying that this kind of code runs in parallel – I think you’re confusing parallelism with asynchronicity. Some people in this StackOverflow conversation can articulate the difference better than I.
Why is it a terrible idea? It lets you take advantage of non-blocking I/O pretty well without wasting CPU cycles spawning another thread. That is totally how Node.js works (an exemplary platform that uses this approach); are you saying Node.js doesn’t work like people and businesses are claiming?
I’m pretty sure I understand that generators are still single-threaded. I never said otherwise. Of course only one statement of code can be executed at a time.
That’s right, if those database calls or HTTP calls are blocking. That’s why you use non-blocking I/O to do those things instead. That’s the whole point of the asynchronous model.
You don’t get non-blocking I/O for free when using coroutines. That’s the whole problem with your article. It’s sensationalist.
Again, I don’t claim that. Coroutines can let you take advantage of non-blocking I/O though and make the best use of your CPU cycles. See Icicle for an example of coroutines to accomplish this.
You are 100% incorrect. Coroutines perfectly embody the definition for asynchronous code. What coroutines are not is “parallel.” It seems you’re the one with your terms mixed up.
The defition of asynchronous:
“of or requiring a form of computer control timing protocol in which a specific operation begins upon receipt of an indication (signal) that the preceding operation has been completed.”
This is exactly what coroutines do. At no point in the article does Stephen claim this is “parallel” execution (which is what you seem to have confused with async).
An asynchronous operation is simply one whose result arrives at some point in the future other than when it was dispatched (thereby allowing other computations to take place in the interim). A Promise by its very existence implies asynchronicity – if it didn’t there would be no reason for it to even be a thing. All results would be exactly that: end results.
No, it’s not synchronous. If it’s possible to receive the result of the delegated task at some point in the future while still performing other operations it is asynchronous by any definition of the word. Just because generator functions and promise implementations don’t act as the actual mechanism for concurrent execution (the non-blocking event loop does this) it doesn’t make the end result any less asynchronous.
IO is always blocking in PHP, because there will always be a single thread handling that. Is that so hard to understand?
The only way to make true async code is by leveraging IPC features.
IO is always blocking in PHP? Interesting idea. What do you think the
stream_set_blocking()
function does then?Non-blocking operations are not the same as threading. Blocking simply means that the thread is blocked during a blocking call. Non-blocking means the thread isn’t blocked during the call. That’s all. Most non-blocking operations are non-blocking because they simply check the status of the operation instead of waiting for it to finish. Again, I recommend you research what non-blocking I/O is and how it works.
If you live in a dream world where you can talk to your own code or databases witjh streams. If your controller calls a function that is blocking, it will block. If you request handling is non-blocking, doesn’t mean that the response won’t be. By using this, and React, and so on, you’re just reinventing the wheel that nginx already did. And it’s multithreaded.
Thank you very much for this article! I found it while researching the usage of Generators for pausing/resuming execution of methods to allow for outside providing of values. That way, many similar requirements for database values can be packed in a few requests without doing it manually. I already use this in a project, however, without the ability to pause the execution, I had to use quite a clumsy and verbose code. Generators are just what I need to make the code slim. I, too, hope that future versions of PHP will provide better keywords.
My proof of concept code looks like this: http://pastebin.com/54cw2XHd