{"action":"opened","issue":{"url":"https://api.github.com/repos/php-http/curl-client/issues/32","repository_url":"https://api.github.com/repos/php-http/curl-client","labels_url":"https://api.github.com/repos/php-http/curl-client/issues/32/labels{/name}","comments_url":"https://api.github.com/repos/php-http/curl-client/issues/32/comments","events_url":"https://api.github.com/repos/php-http/curl-client/issues/32/events","html_url":"https://github.com/php-http/curl-client/issues/32","id":231698995,"number":32,"title":"Promise chaining is broken","user":{"login":"jswofford","id":16737788,"avatar_url":"https://avatars0.githubusercontent.com/u/16737788?v=3","gravatar_id":"","url":"https://api.github.com/users/jswofford","html_url":"https://github.com/jswofford","followers_url":"https://api.github.com/users/jswofford/followers","following_url":"https://api.github.com/users/jswofford/following{/other_user}","gists_url":"https://api.github.com/users/jswofford/gists{/gist_id}","starred_url":"https://api.github.com/users/jswofford/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jswofford/subscriptions","organizations_url":"https://api.github.com/users/jswofford/orgs","repos_url":"https://api.github.com/users/jswofford/repos","events_url":"https://api.github.com/users/jswofford/events{/privacy}","received_events_url":"https://api.github.com/users/jswofford/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2017-05-26T18:15:07Z","updated_at":"2017-05-26T18:15:07Z","closed_at":null,"body":"Chaining promises appears to be either broken or unsupported.  The documentation claims general Promise/A+ compliance, so I'm assuming the former.  Regardless, I noticed that the standard ErrorPlugin was not behaving properly when curl-client was used as the client implementation.  Execution of the plugin stack simply _stopped_ when ErrorPlugin threw an exception.\r\n\r\nHere's an example of the issue:\r\n\r\n```php\r\nuse Http\\Client\\Common\\PluginClient;\r\nuse Http\\Client\\Common\\Plugin\\ErrorPlugin;\r\nuse Http\\Client\\Curl\\Client;\r\nuse Zend\\Diactoros\\Request;\r\nuse Zend\\Diactoros\\Response\\TextResponse;\r\n\r\n$client = new PluginClient(new Client(), [new ErrorPlugin()]);\r\n$promise = $client->sendAsyncRequest(new Request('http://localhost:10000/bogus.php', 'GET'))->then(null, function () {\r\n    return new TextResponse('test');\r\n});\r\n\r\n// Results in ClientErrorException, which should be averted by the onRejected callback above.\r\nvar_dump((string) $promise->wait()->getBody());\r\n```\r\n\r\nAfter digging through the code, it looks like the root problem is that all promises in a chain share the same PromiseCore object.  PromiseCore simply executes all onFulfilled callbacks from all promises in the order they were created when a valid HTTP response is received, or all onRejected callbacks if the request was unsuccessful.  As such, all promises that share that core return the same response or throw the same exception.  This approach has a number of problems:\r\n\r\n- Promise results are not immutable.\r\n- Either all onFulfilled callbacks are run, or all onRejected callbacks are run.  There is no evaluation of which callback to execute at each step of the chain.\r\n- Multiple chains branching off the same promise interfere with each other.\r\n- Exceptions thrown by onFulfilled callbacks (like with ErrorPlugin) cause catastrophic failures.\r\n\r\nPoint 4 also leads to a far more insidious error that can occur when issuing multiple asynchronous requests at once.  This might technically qualify as a separate issue since it stems from the handling of curl_multi_info_read, but I'm mentioning it here since it's a consequence of exceptions thrown from onFulfilled callbacks.  Exceptions triggered during the resolution of one response can be thrown when requesting the resolution of a _completely different response_.\r\n\r\n```php\r\nuse Http\\Client\\Common\\PluginClient;\r\nuse Http\\Client\\Common\\Plugin\\ErrorPlugin;\r\nuse Http\\Client\\Curl\\Client;\r\nuse Zend\\Diactoros\\Request;\r\n\r\n$client = new PluginClient(new Client(), [new ErrorPlugin()]);\r\n$bad = $client->sendAsyncRequest(new Request('http://localhost:10000/bogus.php', 'GET'));\r\n$good = $client->sendAsyncRequest(new Request('http://localhost:10000/server.php', 'GET'));\r\n\r\n// Results in ClientErrorException from failure of $bad request.\r\nvar_dump((string) $good->wait()->getBody());\r\n```\r\n\r\nI'm presently working on a fix, but I wanted to make the issue known."}}
{"actor":{"display_login":"jswofford"}}