Showing preview only (1,702K chars total). Download the full file or copy to clipboard to get everything.
Repository: azat-io/you-dont-know-js-ru
Branch: master
Commit: d763a343b438
Files: 67
Total size: 1.6 MB
Directory structure:
gitextract_bdk32l8f/
├── .gitignore
├── LICENSE.txt
├── README.md
├── async & performance/
│ ├── README.md
│ ├── apA.md
│ ├── apB.md
│ ├── apC.md
│ ├── ch1.md
│ ├── ch2.md
│ ├── ch3.md
│ ├── ch4.md
│ ├── ch5.md
│ ├── ch6.md
│ ├── foreword.md
│ └── toc.md
├── es6 & beyond/
│ ├── README.md
│ ├── apA.md
│ ├── ch1.md
│ ├── ch2.md
│ ├── ch3.md
│ ├── ch4.md
│ ├── ch5.md
│ ├── ch6.md
│ ├── ch7.md
│ ├── ch8.md
│ ├── foreword.md
│ └── toc.md
├── preface.md
├── scope & closures/
│ ├── README.md
│ ├── apA.md
│ ├── apB.md
│ ├── apC.md
│ ├── apD.md
│ ├── ch1.md
│ ├── ch2.md
│ ├── ch3.md
│ ├── ch4.md
│ ├── ch5.md
│ └── toc.md
├── this & object prototypes/
│ ├── README.md
│ ├── apA.md
│ ├── apB.md
│ ├── ch1.md
│ ├── ch2.md
│ ├── ch3.md
│ ├── ch4.md
│ ├── ch5.md
│ ├── ch6.md
│ ├── foreword.md
│ └── toc.md
├── types & grammar/
│ ├── README.md
│ ├── apA.md
│ ├── apB.md
│ ├── ch1.md
│ ├── ch2.md
│ ├── ch3.md
│ ├── ch4.md
│ ├── ch5.md
│ ├── foreword.md
│ └── toc.md
└── up & going/
├── README.md
├── apA.md
├── ch1.md
├── ch2.md
├── ch3.md
├── foreword.md
└── toc.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
================================================
FILE: LICENSE.txt
================================================
Creative Commons
Attribution-NonCommercial-NoDerivs 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
1. Definitions
"Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License.
"Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License.
"Distribute" means to make available to the public the original and copies of the Work through sale or other transfer of ownership.
"Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License.
"Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast.
"Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work.
"You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
"Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images.
"Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; and,
to Distribute and Publicly Perform the Work including as incorporated in Collections.
The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats, but otherwise you have no rights to make Adaptations. Subject to 8(f), all rights not expressly granted by Licensor are hereby reserved, including but not limited to the rights set forth in Section 4(d).
4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested.
You may not exercise any of the rights granted to You in Section 3 above in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. The exchange of the Work for other copyrighted works by means of digital file-sharing or otherwise shall not be considered to be intended for or directed toward commercial advantage or private monetary compensation, provided there is no payment of any monetary compensation in connection with the exchange of copyrighted works.
If You Distribute, or Publicly Perform the Work or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work. The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Collection, at a minimum such credit will appear, if a credit for all contributing authors of Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
For the avoidance of doubt:
Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License;
Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License if Your exercise of such rights is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(b) and otherwise waives the right to collect royalties through any statutory or compulsory licensing scheme; and,
Voluntary License Schemes. The Licensor reserves the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License that is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(b).
Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
8. Miscellaneous
Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License.
Creative Commons may be contacted at http://creativecommons.org/.
================================================
FILE: README.md
================================================
# Вы не знаете JS (серия книг)
Представляю вам серию книг, погружение в которую позволит вам окунуться в основные механизмы языка JavaScript. Первое издание серии книг в настоящий момент завершено.
<a href="http://shop.oreilly.com/product/0636920039303.do"><img src="up %26 going/cover.jpg" width="75"></a>
<a href="http://shop.oreilly.com/product/0636920026327.do"><img src="scope %26 closures/cover.jpg" width="75"></a>
<a href="http://shop.oreilly.com/product/0636920033738.do"><img src="this %26 object prototypes/cover.jpg" width="75"></a>
<a href="http://shop.oreilly.com/product/0636920033745.do"><img src="types %26 grammar/cover.jpg" width="75"></a>
<a href="http://shop.oreilly.com/product/0636920033752.do"><img src="async %26 performance/cover.jpg" width="75"></a>
<a href="http://shop.oreilly.com/product/0636920033769.do"><img src="es6 %26 beyond/cover.jpg" width="75"></a>
Не стесняйтесь вносить свой вклад в улучшение качества материала данной серии книг, отправляйте свои PR для улучшения фрагментов кода, пояснений и т.д. Исправление опечаток также приветствуется.
**О том, что послужило причиной для написания данной серии книг, вы можете узнать из [Предисловия](preface.md).**
## Содержимое
* Читать онлайн (бесплатно!): [«Начните и Совершенствуйтесь»](up%20%26%20going/README.md#Вы-не-знаете-js-Начните-и-Совершенствуйтесь), Опубликовано: [Купить сейчас](http://shop.oreilly.com/product/0636920039303.do) в бумажном варианте, электронная книга бесплатна!
* Читать онлайн (бесплатно!): [«Область Видимости и Замыкания»](scope%20%26%20closures/README.md#Вы-не-знаете-js-Область-видимости-и-замыкания), Опубликовано: [Купить сейчас](http://shop.oreilly.com/product/0636920026327.do)
* Читать онлайн (бесплатно!): [«This и Прототипы Объектов»](this%20%26%20object%20prototypes/README.md#you-dont-know-js-this--object-prototypes), Опубликовано: [Купить сейчас](http://shop.oreilly.com/product/0636920033738.do)
* Читать онлайн (бесплатно!): [«Типы и Синтаксис»](types%20%26%20grammar/README.md#you-dont-know-js-types--grammar), Опубликовано: [Купить сейчас](http://shop.oreilly.com/product/0636920033745.do)
* Читать онлайн (бесплатно!): [«Асинхронность и Производительность»](async%20%26%20performance/README.md#you-dont-know-js-async--performance), Опубликовано: [Купить сейчас](http://shop.oreilly.com/product/0636920033752.do)
* Читать онлайн (бесплатно!): [«ES6 и не только»](es6%20%26%20beyond/README.md#you-dont-know-js-es6--beyond), Опубликовано: [Купить сейчас](https://www.ozon.ru/context/detail/id/137473815/)
## Издательство
Серия книг свободно распространяется для чтения, но редактируется, производится и публикуется в книжном издательстве O'Reilly.
Если вам нравится содержание книг, и вы хотите поддержать развитие серии, пожалуйста, приобретите книги, как только они будут доступны для продажи, там, где вы обычно их покупаете. :)
Если же вы хотите поддержать данную работу финансово, но не хотите покупать книги, вы можете это сделать через сервис [Patreon](https://www.patreon.com/getify); я буду глубоко признателен вам за вашу щедрость.
<a href="https://www.patreon.com/getify">[](https://www.patreon.com/getify)</a>
## Очное обучение
Содержание этих книг в значительной степени состоит из учебных материалов, которые я преподаю профессионально в формате следующих семинаров (как бесплатных, так и платных): "Deep JavaScript Foundations", "Rethinking Async" и "ES6: The Right Parts".
Если вам нравятся мои книги, и вы бы хотели связаться со мной по поводу очного обучения в рамках вышеупомянутого учебного курса или других различных моих семинаров по JS / HTML5 / Node.JS, пожалуйста, обратитесь ко мне по электронной почте getify@gmail.com
## Онлайн обучение
Кроме этого, я также преподаю некоторый учебный материал в формате видео. Я веду дистанционные курсы на сервисе [Frontend Masters](https://FrontendMasters.com), такие как [Глубокое понимание основ JavaScript](https://frontendmasters.com/courses/javascript-foundations/). Вы можете найти [все мои курсы здесь](https://frontendmasters.com/kyle-simpson/).
Некоторые из этих курсов также доступны на других платформах, таких как Pluralsight, Lynda.com и O'Reilly Safari Online.
## Вклад в развитие контента
Любой вклад в развитие данного проекта **будет высоко оценён**.
Тем не менее, учитывайте то, что если вы желаете внести свой вклад в содержание книг (а не только исправить опечатки) в этом репозитории, вы соглашаетесь на то, что вы даёте мне абсолютные права на использование добавленного вами контента для серии книг таким образом, каким я (и O'Reilly) сочту это целесообразным.
Бла-бла-бла... :)
## Лицензия и Авторские права
Автор материалов данной серии книг © 2013-2015 Kyle Simpson.
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a><br />Лицензируется в соответствии с <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.
================================================
FILE: async & performance/README.md
================================================
# Вы не знаете JS: Асинхронность и Производительность
<img src="cover.jpg" width="300">
-----
**[Купить цифровую или печатную книгу от издательства O'Reilly (англ.)](http://shop.oreilly.com/product/0636920033752.do)**
-----
[Оглавление](toc.md)
* [Введение](foreword.md) (от [Jake Archibald](http://jakearchibald.com))
* [Предисловие](../preface.md)
* [Глава 1: Асинхронность: Сейчас и Тогда](ch1.md)
* [Глава 2: Колбеки](ch2.md)
* [Глава 3: Обещания](ch3.md)
* [Глава 4: Генераторы](ch4.md)
* [Глава 5: Производительность программы](ch5.md)
* [Глава 6: Бенчмаркинг и настройка](ch6.md)
* [Приложение A: Библиотека: asynquence](apA.md)
* [Приложение B: Расширенные асинхронные шаблоны](apB.md)
* [Приложение C: Благодарности!](apC.md)
================================================
FILE: async & performance/apA.md
================================================
# You Don't Know JS: Async & Performance
# Appendix A: *asynquence* Library
Chapters 1 and 2 went into quite a bit of detail about typical asynchronous programming patterns and how they're commonly solved with callbacks. But we also saw why callbacks are fatally limited in capability, which led us to Chapters 3 and 4, with Promises and generators offering a much more solid, trustable, and reason-able base to build your asynchrony on.
I referenced my own asynchronous library *asynquence* (http://github.com/getify/asynquence) -- "async" + "sequence" = "asynquence" -- several times in this book, and I want to now briefly explain how it works and why its unique design is important and helpful.
In the next appendix, we'll explore some advanced async patterns, but you'll probably want a library to make those palatable enough to be useful. We'll use *asynquence* to express those patterns, so you'll want to spend a little time here getting to know the library first.
*asynquence* is obviously not the only option for good async coding; certainly there are many great libraries in this space. But *asynquence* provides a unique perspective by combining the best of all these patterns into a single library, and moreover is built on a single basic abstraction: the (async) sequence.
My premise is that sophisticated JS programs often need bits and pieces of various different asynchronous patterns woven together, and this is usually left entirely up to each developer to figure out. Instead of having to bring in two or more different async libraries that focus on different aspects of asynchrony, *asynquence* unifies them into variated sequence steps, with just one core library to learn and deploy.
I believe the value is strong enough with *asynquence* to make async flow control programming with Promise-style semantics super easy to accomplish, so that's why we'll exclusively focus on that library here.
To begin, I'll explain the design principles behind *asynquence*, and then we'll illustrate how its API works with code examples.
## Sequences, Abstraction Design
Understanding *asynquence* begins with understanding a fundamental abstraction: any series of steps for a task, whether they separately are synchronous or asynchronous, can be collectively thought of as a "sequence". In other words, a sequence is a container that represents a task, and is comprised of individual (potentially async) steps to complete that task.
Each step in the sequence is controlled under the covers by a Promise (see Chapter 3). That is, every step you add to a sequence implicitly creates a Promise that is wired to the previous end of the sequence. Because of the semantics of Promises, every single step advancement in a sequence is asynchronous, even if you synchronously complete the step.
Moreover, a sequence will always proceed linearly from step to step, meaning that step 2 always comes after step 1 finishes, and so on.
Of course, a new sequence can be forked off an existing sequence, meaning the fork only occurs once the main sequence reaches that point in the flow. Sequences can also be combined in various ways, including having one sequence subsumed by another sequence at a particular point in the flow.
A sequence is kind of like a Promise chain. However, with Promise chains, there is no "handle" to grab that references the entire chain. Whichever Promise you have a reference to only represents the current step in the chain plus any other steps hanging off it. Essentially, you cannot hold a reference to a Promise chain unless you hold a reference to the first Promise in the chain.
There are many cases where it turns out to be quite useful to have a handle that references the entire sequence collectively. The most important of those cases is with sequence abort/cancel. As we covered extensively in Chapter 3, Promises themselves should never be able to be canceled, as this violates a fundamental design imperative: external immutability.
But sequences have no such immutability design principle, mostly because sequences are not passed around as future-value containers that need immutable value semantics. So sequences are the proper level of abstraction to handle abort/cancel behavior. *asynquence* sequences can be `abort()`ed at any time, and the sequence will stop at that point and not go for any reason.
There's plenty more reasons to prefer a sequence abstraction on top of Promise chains, for flow control purposes.
First, Promise chaining is a rather manual process -- one that can get pretty tedious once you start creating and chaining Promises across a wide swath of your programs -- and this tedium can act counterproductively to dissuade the developer from using Promises in places where they are quite appropriate.
Abstractions are meant to reduce boilerplate and tedium, so the sequence abstraction is a good solution to this problem. With Promises, your focus is on the individual step, and there's little assumption that you will keep the chain going. With sequences, the opposite approach is taken, assuming the sequence will keep having more steps added indefinitely.
This abstraction complexity reduction is especially powerful when you start thinking about higher-order Promise patterns (beyond `race([..])` and `all([..])`.
For example, in the middle of a sequence, you may want to express a step that is conceptually like a `try..catch` in that the step will always result in success, either the intended main success resolution or a positive nonerror signal for the caught error. Or, you might want to express a step that is like a retry/until loop, where it keeps trying the same step over and over until success occurs.
These sorts of abstractions are quite nontrivial to express using only Promise primitives, and doing so in the middle of an existing Promise chain is not pretty. But if you abstract your thinking to a sequence, and consider a step as a wrapper around a Promise, that step wrapper can hide such details, freeing you to think about the flow control in the most sensible way without being bothered by the details.
Second, and perhaps more importantly, thinking of async flow control in terms of steps in a sequence allows you to abstract out the details of what types of asynchronicity are involved with each individual step. Under the covers, a Promise will always control the step, but above the covers, that step can look either like a continuation callback (the simple default), or like a real Promise, or as a run-to-completion generator, or ... Hopefully, you get the picture.
Third, sequences can more easily be twisted to adapt to different modes of thinking, such as event-, stream-, or reactive-based coding. *asynquence* provides a pattern I call "reactive sequences" (which we'll cover later) as a variation on the "reactive observable" ideas in RxJS ("Reactive Extensions"), that lets a repeatable event fire off a new sequence instance each time. Promises are one-shot-only, so it's quite awkward to express repetitious asynchrony with Promises alone.
Another alternate mode of thinking inverts the resolution/control capability in a pattern I call "iterable sequences". Instead of each individual step internally controlling its own completion (and thus advancement of the sequence), the sequence is inverted so the advancement control is through an external iterator, and each step in the *iterable sequence* just responds to the `next(..)` *iterator* control.
We'll explore all of these different variations as we go throughout the rest of this appendix, so don't worry if we ran over those bits far too quickly just now.
The takeaway is that sequences are a more powerful and sensible abstraction for complex asynchrony than just Promises (Promise chains) or just generators, and *asynquence* is designed to express that abstraction with just the right level of sugar to make async programming more understandable and more enjoyable.
## *asynquence* API
To start off, the way you create a sequence (an *asynquence* instance) is with the `ASQ(..)` function. An `ASQ()` call with no parameters creates an empty initial sequence, whereas passing one or more values or functions to `ASQ(..)` sets up the sequence with each argument representing the initial steps of the sequence.
**Note:** For the purposes of all code examples here, I will use the *asynquence* top-level identifier in global browser usage: `ASQ`. If you include and use *asynquence* through a module system (browser or server), you of course can define whichever symbol you prefer, and *asynquence* won't care!
Many of the API methods discussed here are built into the core of *asynquence*, but others are provided through including the optional "contrib" plug-ins package. See the documentation for *asynquence* for whether a method is built in or defined via plug-in: http://github.com/getify/asynquence
### Steps
If a function represents a normal step in the sequence, that function is invoked with the first parameter being the continuation callback, and any subsequent parameters being any messages passed on from the previous step. The step will not complete until the continuation callback is called. Once it's called, any arguments you pass to it will be sent along as messages to the next step in the sequence.
To add an additional normal step to the sequence, call `then(..)` (which has essentially the exact same semantics as the `ASQ(..)` call):
```js
ASQ(
// step 1
function(done){
setTimeout( function(){
done( "Hello" );
}, 100 );
},
// step 2
function(done,greeting) {
setTimeout( function(){
done( greeting + " World" );
}, 100 );
}
)
// step 3
.then( function(done,msg){
setTimeout( function(){
done( msg.toUpperCase() );
}, 100 );
} )
// step 4
.then( function(done,msg){
console.log( msg ); // HELLO WORLD
} );
```
**Note:** Though the name `then(..)` is identical to the native Promises API, this `then(..)` is different. You can pass as few or as many functions or values to `then(..)` as you'd like, and each is taken as a separate step. There's no two-callback fulfilled/rejected semantics involved.
Unlike with Promises, where to chain one Promise to the next you have to create and `return` that Promise from a `then(..)` fulfillment handler, with *asynquence*, all you need to do is call the continuation callback -- I always call it `done()` but you can name it whatever suits you -- and optionally pass it completion messages as arguments.
Each step defined by `then(..)` is assumed to be asynchronous. If you have a step that's synchronous, you can either just call `done(..)` right away, or you can use the simpler `val(..)` step helper:
```js
// step 1 (sync)
ASQ( function(done){
done( "Hello" ); // manually synchronous
} )
// step 2 (sync)
.val( function(greeting){
return greeting + " World";
} )
// step 3 (async)
.then( function(done,msg){
setTimeout( function(){
done( msg.toUpperCase() );
}, 100 );
} )
// step 4 (sync)
.val( function(msg){
console.log( msg );
} );
```
As you can see, `val(..)`-invoked steps don't receive a continuation callback, as that part is assumed for you -- and the parameter list is less cluttered as a result! To send a message along to the next step, you simply use `return`.
Think of `val(..)` as representing a synchronous "value-only" step, which is useful for synchronous value operations, logging, and the like.
### Errors
One important difference with *asynquence* compared to Promises is with error handling.
With Promises, each individual Promise (step) in a chain can have its own independent error, and each subsequent step has the ability to handle the error or not. The main reason for this semantic comes (again) from the focus on individual Promises rather than on the chain (sequence) as a whole.
I believe that most of the time, an error in one part of a sequence is generally not recoverable, so the subsequent steps in the sequence are moot and should be skipped. So, by default, an error at any step of a sequence throws the entire sequence into error mode, and the rest of the normal steps are ignored.
If you *do* need to have a step where its error is recoverable, there are several different API methods that can accommodate, such as `try(..)` -- previously mentioned as a kind of `try..catch` step -- or `until(..)` -- a retry loop that keeps attempting the step until it succeeds or you manually `break()` the loop. *asynquence* even has `pThen(..)` and `pCatch(..)` methods, which work identically to how normal Promise `then(..)` and `catch(..)` work (see Chapter 3), so you can do localized mid-sequence error handling if you so choose.
The point is, you have both options, but the more common one in my experience is the default. With Promises, to get a chain of steps to ignore all steps once an error occurs, you have to take care not to register a rejection handler at any step; otherwise, that error gets swallowed as handled, and the sequence may continue (perhaps unexpectedly). This kind of desired behavior is a bit awkward to properly and reliably handle.
To register a sequence error notification handler, *asynquence* provides an `or(..)` sequence method, which also has an alias of `onerror(..)`. You can call this method anywhere in the sequence, and you can register as many handlers as you'd like. That makes it easy for multiple different consumers to listen in on a sequence to know if it failed or not; it's kind of like an error event handler in that respect.
Just like with Promises, all JS exceptions become sequence errors, or you can programmatically signal a sequence error:
```js
var sq = ASQ( function(done){
setTimeout( function(){
// signal an error for the sequence
done.fail( "Oops" );
}, 100 );
} )
.then( function(done){
// will never get here
} )
.or( function(err){
console.log( err ); // Oops
} )
.then( function(done){
// won't get here either
} );
// later
sq.or( function(err){
console.log( err ); // Oops
} );
```
Another really important difference with error handling in *asynquence* compared to native Promises is the default behavior of "unhandled exceptions". As we discussed at length in Chapter 3, a rejected Promise without a registered rejection handler will just silently hold (aka swallow) the error; you have to remember to always end a chain with a final `catch(..)`.
In *asynquence*, the assumption is reversed.
If an error occurs on a sequence, and it **at that moment** has no error handlers registered, the error is reported to the `console`. In other words, unhandled rejections are by default always reported so as not to be swallowed and missed.
As soon as you register an error handler against a sequence, it opts that sequence out of such reporting, to prevent duplicate noise.
There may, in fact, be cases where you want to create a sequence that may go into the error state before you have a chance to register the handler. This isn't common, but it can happen from time to time.
In those cases, you can also **opt a sequence instance out** of error reporting by calling `defer()` on the sequence. You should only opt out of error reporting if you are sure that you're going to eventually handle such errors:
```js
var sq1 = ASQ( function(done){
doesnt.Exist(); // will throw exception to console
} );
var sq2 = ASQ( function(done){
doesnt.Exist(); // will throw only a sequence error
} )
// opt-out of error reporting
.defer();
setTimeout( function(){
sq1.or( function(err){
console.log( err ); // ReferenceError
} );
sq2.or( function(err){
console.log( err ); // ReferenceError
} );
}, 100 );
// ReferenceError (from sq1)
```
This is better error handling behavior than Promises themselves have, because it's the Pit of Success, not the Pit of Failure (see Chapter 3).
**Note:** If a sequence is piped into (aka subsumed by) another sequence -- see "Combining Sequences" for a complete description -- then the source sequence is opted out of error reporting, but now the target sequence's error reporting or lack thereof must be considered.
### Parallel Steps
Not all steps in your sequences will have just a single (async) task to perform; some will need to perform multiple steps "in parallel" (concurrently). A step in a sequence in which multiple substeps are processing concurrently is called a `gate(..)` -- there's an `all(..)` alias if you prefer -- and is directly symmetric to native `Promise.all([..])`.
If all the steps in the `gate(..)` complete successfully, all success messages will be passed to the next sequence step. If any of them generate errors, the whole sequence immediately goes into an error state.
Consider:
```js
ASQ( function(done){
setTimeout( done, 100 );
} )
.gate(
function(done){
setTimeout( function(){
done( "Hello" );
}, 100 );
},
function(done){
setTimeout( function(){
done( "World", "!" );
}, 100 );
}
)
.val( function(msg1,msg2){
console.log( msg1 ); // Hello
console.log( msg2 ); // [ "World", "!" ]
} );
```
For illustration, let's compare that example to native Promises:
```js
new Promise( function(resolve,reject){
setTimeout( resolve, 100 );
} )
.then( function(){
return Promise.all( [
new Promise( function(resolve,reject){
setTimeout( function(){
resolve( "Hello" );
}, 100 );
} ),
new Promise( function(resolve,reject){
setTimeout( function(){
// note: we need a [ ] array here
resolve( [ "World", "!" ] );
}, 100 );
} )
] );
} )
.then( function(msgs){
console.log( msgs[0] ); // Hello
console.log( msgs[1] ); // [ "World", "!" ]
} );
```
Yuck. Promises require a lot more boilerplate overhead to express the same asynchronous flow control. That's a great illustration of why the *asynquence* API and abstraction make dealing with Promise steps a lot nicer. The improvement only goes higher the more complex your asynchrony is.
#### Step Variations
There are several variations in the contrib plug-ins on *asynquence*'s `gate(..)` step type that can be quite helpful:
* `any(..)` is like `gate(..)`, except just one segment has to eventually succeed to proceed on the main sequence.
* `first(..)` is like `any(..)`, except as soon as any segment succeeds, the main sequence proceeds (ignoring subsequent results from other segments).
* `race(..)` (symmetric with `Promise.race([..])`) is like `first(..)`, except the main sequence proceeds as soon as any segment completes (either success or failure).
* `last(..)` is like `any(..)`, except only the latest segment to complete successfully sends its message(s) along to the main sequence.
* `none(..)` is the inverse of `gate(..)`: the main sequence proceeds only if all the segments fail (with all segment error message(s) transposed as success message(s) and vice versa).
Let's first define some helpers to make illustration cleaner:
```js
function success1(done) {
setTimeout( function(){
done( 1 );
}, 100 );
}
function success2(done) {
setTimeout( function(){
done( 2 );
}, 100 );
}
function failure3(done) {
setTimeout( function(){
done.fail( 3 );
}, 100 );
}
function output(msg) {
console.log( msg );
}
```
Now, let's demonstrate these `gate(..)` step variations:
```js
ASQ().race(
failure3,
success1
)
.or( output ); // 3
ASQ().any(
success1,
failure3,
success2
)
.val( function(){
var args = [].slice.call( arguments );
console.log(
args // [ 1, undefined, 2 ]
);
} );
ASQ().first(
failure3,
success1,
success2
)
.val( output ); // 1
ASQ().last(
failure3,
success1,
success2
)
.val( output ); // 2
ASQ().none(
failure3
)
.val( output ) // 3
.none(
failure3
success1
)
.or( output ); // 1
```
Another step variation is `map(..)`, which lets you asynchronously map elements of an array to different values, and the step doesn't proceed until all the mappings are complete. `map(..)` is very similar to `gate(..)`, except it gets the initial values from an array instead of from separately specified functions, and also because you define a single function callback to operate on each value:
```js
function double(x,done) {
setTimeout( function(){
done( x * 2 );
}, 100 );
}
ASQ().map( [1,2,3], double )
.val( output ); // [2,4,6]
```
Also, `map(..)` can receive either of its parameters (the array or the callback) from messages passed from the previous step:
```js
function plusOne(x,done) {
setTimeout( function(){
done( x + 1 );
}, 100 );
}
ASQ( [1,2,3] )
.map( double ) // message `[1,2,3]` comes in
.map( plusOne ) // message `[2,4,6]` comes in
.val( output ); // [3,5,7]
```
Another variation is `waterfall(..)`, which is kind of like a mixture between `gate(..)`'s message collection behavior but `then(..)`'s sequential processing.
Step 1 is first executed, then the success message from step 1 is given to step 2, and then both success messages go to step 3, and then all three success messages go to step 4, and so on, such that the messages sort of collect and cascade down the "waterfall".
Consider:
```js
function double(done) {
var args = [].slice.call( arguments, 1 );
console.log( args );
setTimeout( function(){
done( args[args.length - 1] * 2 );
}, 100 );
}
ASQ( 3 )
.waterfall(
double, // [ 3 ]
double, // [ 6 ]
double, // [ 6, 12 ]
double // [ 6, 12, 24 ]
)
.val( function(){
var args = [].slice.call( arguments );
console.log( args ); // [ 6, 12, 24, 48 ]
} );
```
If at any point in the "waterfall" an error occurs, the whole sequence immediately goes into an error state.
#### Error Tolerance
Sometimes you want to manage errors at the step level and not let them necessarily send the whole sequence into the error state. *asynquence* offers two step variations for that purpose.
`try(..)` attempts a step, and if it succeeds, the sequence proceeds as normal, but if the step fails, the failure is turned into a success message formated as `{ catch: .. }` with the error message(s) filled in:
```js
ASQ()
.try( success1 )
.val( output ) // 1
.try( failure3 )
.val( output ) // { catch: 3 }
.or( function(err){
// never gets here
} );
```
You could instead set up a retry loop using `until(..)`, which tries the step and if it fails, retries the step again on the next event loop tick, and so on.
This retry loop can continue indefinitely, but if you want to break out of the loop, you can call the `break()` flag on the completion trigger, which sends the main sequence into an error state:
```js
var count = 0;
ASQ( 3 )
.until( double )
.val( output ) // 6
.until( function(done){
count++;
setTimeout( function(){
if (count < 5) {
done.fail();
}
else {
// break out of the `until(..)` retry loop
done.break( "Oops" );
}
}, 100 );
} )
.or( output ); // Oops
```
#### Promise-Style Steps
If you would prefer to have, inline in your sequence, Promise-style semantics like Promises' `then(..)` and `catch(..)` (see Chapter 3), you can use the `pThen` and `pCatch` plug-ins:
```js
ASQ( 21 )
.pThen( function(msg){
return msg * 2;
} )
.pThen( output ) // 42
.pThen( function(){
// throw an exception
doesnt.Exist();
} )
.pCatch( function(err){
// caught the exception (rejection)
console.log( err ); // ReferenceError
} )
.val( function(){
// main sequence is back in a
// success state because previous
// exception was caught by
// `pCatch(..)`
} );
```
`pThen(..)` and `pCatch(..)` are designed to run in the sequence, but behave as if it was a normal Promise chain. As such, you can either resolve genuine Promises or *asynquence* sequences from the "fulfillment" handler passed to `pThen(..)` (see Chapter 3).
### Forking Sequences
One feature that can be quite useful about Promises is that you can attach multiple `then(..)` handler registrations to the same promise, effectively "forking" the flow-control at that promise:
```js
var p = Promise.resolve( 21 );
// fork 1 (from `p`)
p.then( function(msg){
return msg * 2;
} )
.then( function(msg){
console.log( msg ); // 42
} )
// fork 2 (from `p`)
p.then( function(msg){
console.log( msg ); // 21
} );
```
The same "forking" is easy in *asynquence* with `fork()`:
```js
var sq = ASQ(..).then(..).then(..);
var sq2 = sq.fork();
// fork 1
sq.then(..)..;
// fork 2
sq2.then(..)..;
```
### Combining Sequences
The reverse of `fork()`ing, you can combine two sequences by subsuming one into another, using the `seq(..)` instance method:
```js
var sq = ASQ( function(done){
setTimeout( function(){
done( "Hello World" );
}, 200 );
} );
ASQ( function(done){
setTimeout( done, 100 );
} )
// subsume `sq` sequence into this sequence
.seq( sq )
.val( function(msg){
console.log( msg ); // Hello World
} )
```
`seq(..)` can either accept a sequence itself, as shown here, or a function. If a function, it's expected that the function when called will return a sequence, so the preceding code could have been done with:
```js
// ..
.seq( function(){
return sq;
} )
// ..
```
Also, that step could instead have been accomplished with a `pipe(..)`:
```js
// ..
.then( function(done){
// pipe `sq` into the `done` continuation callback
sq.pipe( done );
} )
// ..
```
When a sequence is subsumed, both its success message stream and its error stream are piped in.
**Note:** As mentioned in an earlier note, piping (manually with `pipe(..)` or automatically with `seq(..)`) opts the source sequence out of error-reporting, but doesn't affect the error reporting status of the target sequence.
## Value and Error Sequences
If any step of a sequence is just a normal value, that value is just mapped to that step's completion message:
```js
var sq = ASQ( 42 );
sq.val( function(msg){
console.log( msg ); // 42
} );
```
If you want to make a sequence that's automatically errored:
```js
var sq = ASQ.failed( "Oops" );
ASQ()
.seq( sq )
.val( function(msg){
// won't get here
} )
.or( function(err){
console.log( err ); // Oops
} );
```
You also may want to automatically create a delayed-value or a delayed-error sequence. Using the `after` and `failAfter` contrib plug-ins, this is easy:
```js
var sq1 = ASQ.after( 100, "Hello", "World" );
var sq2 = ASQ.failAfter( 100, "Oops" );
sq1.val( function(msg1,msg2){
console.log( msg1, msg2 ); // Hello World
} );
sq2.or( function(err){
console.log( err ); // Oops
} );
```
You can also insert a delay in the middle of a sequence using `after(..)`:
```js
ASQ( 42 )
// insert a delay into the sequence
.after( 100 )
.val( function(msg){
console.log( msg ); // 42
} );
```
## Promises and Callbacks
I think *asynquence* sequences provide a lot of value on top of native Promises, and for the most part you'll find it more pleasant and more powerful to work at that level of abstraction. However, integrating *asynquence* with other non-*asynquence* code will be a reality.
You can easily subsume a promise (e.g., thenable -- see Chapter 3) into a sequence using the `promise(..)` instance method:
```js
var p = Promise.resolve( 42 );
ASQ()
.promise( p ) // could also: `function(){ return p; }`
.val( function(msg){
console.log( msg ); // 42
} );
```
And to go the opposite direction and fork/vend a promise from a sequence at a certain step, use the `toPromise` contrib plug-in:
```js
var sq = ASQ.after( 100, "Hello World" );
sq.toPromise()
// this is a standard promise chain now
.then( function(msg){
return msg.toUpperCase();
} )
.then( function(msg){
console.log( msg ); // HELLO WORLD
} );
```
To adapt *asynquence* to systems using callbacks, there are several helper facilities. To automatically generate an "error-first style" callback from your sequence to wire into a callback-oriented utility, use `errfcb`:
```js
var sq = ASQ( function(done){
// note: expecting "error-first style" callback
someAsyncFuncWithCB( 1, 2, done.errfcb )
} )
.val( function(msg){
// ..
} )
.or( function(err){
// ..
} );
// note: expecting "error-first style" callback
anotherAsyncFuncWithCB( 1, 2, sq.errfcb() );
```
You also may want to create a sequence-wrapped version of a utility -- compare to "promisory" in Chapter 3 and "thunkory" in Chapter 4 -- and *asynquence* provides `ASQ.wrap(..)` for that purpose:
```js
var coolUtility = ASQ.wrap( someAsyncFuncWithCB );
coolUtility( 1, 2 )
.val( function(msg){
// ..
} )
.or( function(err){
// ..
} );
```
**Note:** For the sake of clarity (and for fun!), let's coin yet another term, for a sequence-producing function that comes from `ASQ.wrap(..)`, like `coolUtility` here. I propose "sequory" ("sequence" + "factory").
## Iterable Sequences
The normal paradigm for a sequence is that each step is responsible for completing itself, which is what advances the sequence. Promises work the same way.
The unfortunate part is that sometimes you need external control over a Promise/step, which leads to awkward "capability extraction".
Consider this Promises example:
```js
var domready = new Promise( function(resolve,reject){
// don't want to put this here, because
// it belongs logically in another part
// of the code
document.addEventListener( "DOMContentLoaded", resolve );
} );
// ..
domready.then( function(){
// DOM is ready!
} );
```
The "capability extraction" anti-pattern with Promises looks like this:
```js
var ready;
var domready = new Promise( function(resolve,reject){
// extract the `resolve()` capability
ready = resolve;
} );
// ..
domready.then( function(){
// DOM is ready!
} );
// ..
document.addEventListener( "DOMContentLoaded", ready );
```
**Note:** This anti-pattern is an awkward code smell, in my opinion, but some developers like it, for reasons I can't grasp.
*asynquence* offers an inverted sequence type I call "iterable sequences", which externalizes the control capability (it's quite useful in use cases like the `domready`):
```js
// note: `domready` here is an *iterator* that
// controls the sequence
var domready = ASQ.iterable();
// ..
domready.val( function(){
// DOM is ready
} );
// ..
document.addEventListener( "DOMContentLoaded", domready.next );
```
There's more to iterable sequences than what we see in this scenario. We'll come back to them in Appendix B.
## Running Generators
In Chapter 4, we derived a utility called `run(..)` which can run generators to completion, listening for `yield`ed Promises and using them to async resume the generator. *asynquence* has just such a utility built in, called `runner(..)`.
Let's first set up some helpers for illustration:
```js
function doublePr(x) {
return new Promise( function(resolve,reject){
setTimeout( function(){
resolve( x * 2 );
}, 100 );
} );
}
function doubleSeq(x) {
return ASQ( function(done){
setTimeout( function(){
done( x * 2)
}, 100 );
} );
}
```
Now, we can use `runner(..)` as a step in the middle of a sequence:
```js
ASQ( 10, 11 )
.runner( function*(token){
var x = token.messages[0] + token.messages[1];
// yield a real promise
x = yield doublePr( x );
// yield a sequence
x = yield doubleSeq( x );
return x;
} )
.val( function(msg){
console.log( msg ); // 84
} );
```
### Wrapped Generators
You can also create a self-packaged generator -- that is, a normal function that runs your specified generator and returns a sequence for its completion -- by `ASQ.wrap(..)`ing it:
```js
var foo = ASQ.wrap( function*(token){
var x = token.messages[0] + token.messages[1];
// yield a real promise
x = yield doublePr( x );
// yield a sequence
x = yield doubleSeq( x );
return x;
}, { gen: true } );
// ..
foo( 8, 9 )
.val( function(msg){
console.log( msg ); // 68
} );
```
There's a lot more awesome that `runner(..)` is capable of, but we'll come back to that in Appendix B.
## Review
*asynquence* is a simple abstraction -- a sequence is a series of (async) steps -- on top of Promises, aimed at making working with various asynchronous patterns much easier, without any compromise in capability.
There are other goodies in the *asynquence* core API and its contrib plug-ins beyond what we saw in this appendix, but we'll leave that as an exercise for the reader to go check the rest of the capabilities out.
You've now seen the essence and spirit of *asynquence*. The key take away is that a sequence is comprised of steps, and those steps can be any of dozens of different variations on Promises, or they can be a generator-run, or... The choice is up to you, you have all the freedom to weave together whatever async flow control logic is appropriate for your tasks. No more library switching to catch different async patterns.
If these *asynquence* snippets have made sense to you, you're now pretty well up to speed on the library; it doesn't take that much to learn, actually!
If you're still a little fuzzy on how it works (or why!), you'll want to spend a little more time examining the previous examples and playing around with *asynquence* yourself, before going on to the next appendix. Appendix B will push *asynquence* into several more advanced and powerful async patterns.
================================================
FILE: async & performance/apB.md
================================================
# You Don't Know JS: Async & Performance
# Appendix B: Advanced Async Patterns
Appendix A introduced the *asynquence* library for sequence-oriented async flow control, primarily based on Promises and generators.
Now we'll explore other advanced asynchronous patterns built on top of that existing understanding and functionality, and see how *asynquence* makes those sophisticated async techniques easy to mix and match in our programs without needing lots of separate libraries.
## Iterable Sequences
We introduced *asynquence*'s iterable sequences in the previous appendix, but we want to revisit them in more detail.
To refresh, recall:
```js
var domready = ASQ.iterable();
// ..
domready.val( function(){
// DOM is ready
} );
// ..
document.addEventListener( "DOMContentLoaded", domready.next );
```
Now, let's define a sequence of multiple steps as an iterable sequence:
```js
var steps = ASQ.iterable();
steps
.then( function STEP1(x){
return x * 2;
} )
.steps( function STEP2(x){
return x + 3;
} )
.steps( function STEP3(x){
return x * 4;
} );
steps.next( 8 ).value; // 16
steps.next( 16 ).value; // 19
steps.next( 19 ).value; // 76
steps.next().done; // true
```
As you can see, an iterable sequence is a standard-compliant *iterator* (see Chapter 4). So, it can be iterated with an ES6 `for..of` loop, just like a generator (or any other *iterable*) can:
```js
var steps = ASQ.iterable();
steps
.then( function STEP1(){ return 2; } )
.then( function STEP2(){ return 4; } )
.then( function STEP3(){ return 6; } )
.then( function STEP4(){ return 8; } )
.then( function STEP5(){ return 10; } );
for (var v of steps) {
console.log( v );
}
// 2 4 6 8 10
```
Beyond the event triggering example shown in the previous appendix, iterable sequences are interesting because in essence they can be seen as a stand-in for generators or Promise chains, but with even more flexibility.
Consider a multiple Ajax request example -- we've seen the same scenario in Chapters 3 and 4, both as a Promise chain and as a generator, respectively -- expressed as an iterable sequence:
```js
// sequence-aware ajax
var request = ASQ.wrap( ajax );
ASQ( "http://some.url.1" )
.runner(
ASQ.iterable()
.then( function STEP1(token){
var url = token.messages[0];
return request( url );
} )
.then( function STEP2(resp){
return ASQ().gate(
request( "http://some.url.2/?v=" + resp ),
request( "http://some.url.3/?v=" + resp )
);
} )
.then( function STEP3(r1,r2){ return r1 + r2; } )
)
.val( function(msg){
console.log( msg );
} );
```
The iterable sequence expresses a sequential series of (sync or async) steps that looks awfully similar to a Promise chain -- in other words, it's much cleaner looking than just plain nested callbacks, but not quite as nice as the `yield`-based sequential syntax of generators.
But we pass the iterable sequence into `ASQ#runner(..)`, which runs it to completion the same as if it was a generator. The fact that an iterable sequence behaves essentially the same as a generator is notable for a couple of reasons.
First, iterable sequences are kind of a pre-ES6 equivalent to a certain subset of ES6 generators, which means you can either author them directly (to run anywhere), or you can author ES6 generators and transpile/convert them to iterable sequences (or Promise chains for that matter!).
Thinking of an async-run-to-completion generator as just syntactic sugar for a Promise chain is an important recognition of their isomorphic relationship.
Before we move on, we should note that the previous snippet could have been expressed in *asynquence* as:
```js
ASQ( "http://some.url.1" )
.seq( /*STEP 1*/ request )
.seq( function STEP2(resp){
return ASQ().gate(
request( "http://some.url.2/?v=" + resp ),
request( "http://some.url.3/?v=" + resp )
);
} )
.val( function STEP3(r1,r2){ return r1 + r2; } )
.val( function(msg){
console.log( msg );
} );
```
Moreover, step 2 could have even been expressed as:
```js
.gate(
function STEP2a(done,resp) {
request( "http://some.url.2/?v=" + resp )
.pipe( done );
},
function STEP2b(done,resp) {
request( "http://some.url.3/?v=" + resp )
.pipe( done );
}
)
```
So, why would we go to the trouble of expressing our flow control as an iterable sequence in a `ASQ#runner(..)` step, when it seems like a simpler/flatter *asyquence* chain does the job well?
Because the iterable sequence form has an important trick up its sleeve that gives us more capability. Read on.
### Extending Iterable Sequences
Generators, normal *asynquence* sequences, and Promise chains, are all **eagerly evaluated** -- whatever flow control is expressed initially *is* the fixed flow that will be followed.
However, iterable sequences are **lazily evaluated**, which means that during execution of the iterable sequence, you can extend the sequence with more steps if desired.
**Note:** You can only append to the end of an iterable sequence, not inject into the middle of the sequence.
Let's first look at a simpler (synchronous) example of that capability to get familiar with it:
```js
function double(x) {
x *= 2;
// should we keep extending?
if (x < 500) {
isq.then( double );
}
return x;
}
// setup single-step iterable sequence
var isq = ASQ.iterable().then( double );
for (var v = 10, ret;
(ret = isq.next( v )) && !ret.done;
) {
v = ret.value;
console.log( v );
}
```
The iterable sequence starts out with only one defined step (`isq.then(double)`), but the sequence keeps extending itself under certain conditions (`x < 500`). Both *asynquence* sequences and Promise chains technically *can* do something similar, but we'll see in a little bit why their capability is insufficient.
Though this example is rather trivial and could otherwise be expressed with a `while` loop in a generator, we'll consider more sophisticated cases.
For instance, you could examine the response from an Ajax request and if it indicates that more data is needed, you conditionally insert more steps into the iterable sequence to make the additional request(s). Or you could conditionally add a value-formatting step to the end of your Ajax handling.
Consider:
```js
var steps = ASQ.iterable()
.then( function STEP1(token){
var url = token.messages[0].url;
// was an additional formatting step provided?
if (token.messages[0].format) {
steps.then( token.messages[0].format );
}
return request( url );
} )
.then( function STEP2(resp){
// add another Ajax request to the sequence?
if (/x1/.test( resp )) {
steps.then( function STEP5(text){
return request(
"http://some.url.4/?v=" + text
);
} );
}
return ASQ().gate(
request( "http://some.url.2/?v=" + resp ),
request( "http://some.url.3/?v=" + resp )
);
} )
.then( function STEP3(r1,r2){ return r1 + r2; } );
```
You can see in two different places where we conditionally extend `steps` with `steps.then(..)`. And to run this `steps` iterable sequence, we just wire it into our main program flow with an *asynquence* sequence (called `main` here) using `ASQ#runner(..)`:
```js
var main = ASQ( {
url: "http://some.url.1",
format: function STEP4(text){
return text.toUpperCase();
}
} )
.runner( steps )
.val( function(msg){
console.log( msg );
} );
```
Can the flexibility (conditional behavior) of the `steps` iterable sequence be expressed with a generator? Kind of, but we have to rearrange the logic in a slightly awkward way:
```js
function *steps(token) {
// **STEP 1**
var resp = yield request( token.messages[0].url );
// **STEP 2**
var rvals = yield ASQ().gate(
request( "http://some.url.2/?v=" + resp ),
request( "http://some.url.3/?v=" + resp )
);
// **STEP 3**
var text = rvals[0] + rvals[1];
// **STEP 4**
// was an additional formatting step provided?
if (token.messages[0].format) {
text = yield token.messages[0].format( text );
}
// **STEP 5**
// need another Ajax request added to the sequence?
if (/foobar/.test( resp )) {
text = yield request(
"http://some.url.4/?v=" + text
);
}
return text;
}
// note: `*steps()` can be run by the same `ASQ` sequence
// as `steps` was previously
```
Setting aside the already identified benefits of the sequential, synchronous-looking syntax of generators (see Chapter 4), the `steps` logic had to be reordered in the `*steps()` generator form, to fake the dynamicism of the extendable iterable sequence `steps`.
What about expressing the functionality with Promises or sequences, though? You *can* do something like this:
```js
var steps = something( .. )
.then( .. )
.then( function(..){
// ..
// extending the chain, right?
steps = steps.then( .. );
// ..
})
.then( .. );
```
The problem is subtle but important to grasp. So, consider trying to wire up our `steps` Promise chain into our main program flow -- this time expressed with Promises instead of *asynquence*:
```js
var main = Promise.resolve( {
url: "http://some.url.1",
format: function STEP4(text){
return text.toUpperCase();
}
} )
.then( function(..){
return steps; // hint!
} )
.val( function(msg){
console.log( msg );
} );
```
Can you spot the problem now? Look closely!
There's a race condition for sequence steps ordering. When you `return steps`, at that moment `steps` *might* be the originally defined promise chain, or it might now point to the extended promise chain via the `steps = steps.then(..)` call, depending on what order things happen.
Here are the two possible outcomes:
* If `steps` is still the original promise chain, once it's later "extended" by `steps = steps.then(..)`, that extended promise on the end of the chain is **not** considered by the `main` flow, as it's already tapped the `steps` chain. This is the unfortunately limiting **eager evaluation**.
* If `steps` is already the extended promise chain, it works as we expect in that the extended promise is what `main` taps.
Other than the obvious fact that a race condition is intolerable, the first case is the concern; it illustrates **eager evaluation** of the promise chain. By contrast, we easily extended the iterable sequence without such issues, because iterable sequences are **lazily evaluated**.
The more dynamic you need your flow control, the more iterable sequences will shine.
**Tip:** Check out more information and examples of iterable sequences on the *asynquence* site (https://github.com/getify/asynquence/blob/master/README.md#iterable-sequences).
## Event Reactive
It should be obvious from (at least!) Chapter 3 that Promises are a very powerful tool in your async toolbox. But one thing that's clearly lacking is in their capability to handle streams of events, as a Promise can only be resolved once. And frankly, this exact same weakness is true of plain *asynquence* sequences, as well.
Consider a scenario where you want to fire off a series of steps every time a certain event is fired. A single Promise or sequence cannot represent all occurrences of that event. So, you have to create a whole new Promise chain (or sequence) for *each* event occurrence, such as:
```js
listener.on( "foobar", function(data){
// create a new event handling promise chain
new Promise( function(resolve,reject){
// ..
} )
.then( .. )
.then( .. );
} );
```
The base functionality we need is present in this approach, but it's far from a desirable way to express our intended logic. There are two separate capabilities conflated in this paradigm: the event listening, and responding to the event; separation of concerns would implore us to separate out these capabilities.
The carefully observant reader will see this problem as somewhat symmetrical to the problems we detailed with callbacks in Chapter 2; it's kind of an inversion of control problem.
Imagine uninverting this paradigm, like so:
```js
var observable = listener.on( "foobar" );
// later
observable
.then( .. )
.then( .. );
// elsewhere
observable
.then( .. )
.then( .. );
```
The `observable` value is not exactly a Promise, but you can *observe* it much like you can observe a Promise, so it's closely related. In fact, it can be observed many times, and it will send out notifications every time its event (`"foobar"`) occurs.
**Tip:** This pattern I've just illustrated is a **massive simplification** of the concepts and motivations behind reactive programming (aka RP), which has been implemented/expounded upon by several great projects and languages. A variation on RP is functional reactive programming (FRP), which refers to applying functional programming techniques (immutability, referential integrity, etc.) to streams of data. "Reactive" refers to spreading this functionality out over time in response to events. The interested reader should consider studying "Reactive Observables" in the fantastic "Reactive Extensions" library ("RxJS" for JavaScript) by Microsoft (http://rxjs.codeplex.com/); it's much more sophisticated and powerful than I've just shown. Also, Andre Staltz has an excellent write-up (https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) that pragmatically lays out RP in concrete examples.
### ES7 Observables
At the time of this writing, there's an early ES7 proposal for a new data type called "Observable" (https://github.com/jhusain/asyncgenerator#introducing-observable), which in spirit is similar to what we've laid out here, but is definitely more sophisticated.
The notion of this kind of Observable is that the way you "subscribe" to the events from a stream is to pass in a generator -- actually the *iterator* is the interested party -- whose `next(..)` method will be called for each event.
You could imagine it sort of like this:
```js
// `someEventStream` is a stream of events, like from
// mouse clicks, and the like.
var observer = new Observer( someEventStream, function*(){
while (var evt = yield) {
console.log( evt );
}
} );
```
The generator you pass in will `yield` pause the `while` loop waiting for the next event. The *iterator* attached to the generator instance will have its `next(..)` called each time `someEventStream` has a new event published, and so that event data will resume your generator/*iterator* with the `evt` data.
In the subscription to events functionality here, it's the *iterator* part that matters, not the generator. So conceptually you could pass in practically any iterable, including `ASQ.iterable()` iterable sequences.
Interestingly, there are also proposed adapters to make it easy to construct Observables from certain types of streams, such as `fromEvent(..)` for DOM events. If you look at a suggested implementation of `fromEvent(..)` in the earlier linked ES7 proposal, it looks an awful lot like the `ASQ.react(..)` we'll see in the next section.
Of course, these are all early proposals, so what shakes out may very well look/behave differently than shown here. But it's exciting to see the early alignments of concepts across different libraries and language proposals!
### Reactive Sequences
With that crazy brief summary of Observables (and F/RP) as our inspiration and motivation, I will now illustrate an adaptation of a small subset of "Reactive Observables," which I call "Reactive Sequences."
First, let's start with how to create an Observable, using an *asynquence* plug-in utility called `react(..)`:
```js
var observable = ASQ.react( function setup(next){
listener.on( "foobar", next );
} );
```
Now, let's see how to define a sequence that "reacts" -- in F/RP, this is typically called "subscribing" -- to that `observable`:
```js
observable
.seq( .. )
.then( .. )
.val( .. );
```
So, you just define the sequence by chaining off the Observable. That's easy, huh?
In F/RP, the stream of events typically channels through a set of functional transforms, like `scan(..)`, `map(..)`, `reduce(..)`, and so on. With reactive sequences, each event channels through a new instance of the sequence. Let's look at a more concrete example:
```js
ASQ.react( function setup(next){
document.getElementById( "mybtn" )
.addEventListener( "click", next, false );
} )
.seq( function(evt){
var btnID = evt.target.id;
return request(
"http://some.url.1/?id=" + btnID
);
} )
.val( function(text){
console.log( text );
} );
```
The "reactive" portion of the reactive sequence comes from assigning one or more event handlers to invoke the event trigger (calling `next(..)`).
The "sequence" portion of the reactive sequence is exactly like the sequences we've already explored: each step can be whatever asynchronous technique makes sense, from continuation callback to Promise to generator.
Once you set up a reactive sequence, it will continue to initiate instances of the sequence as long as the events keep firing. If you want to stop a reactive sequence, you can call `stop()`.
If a reactive sequence is `stop()`'d, you likely want the event handler(s) to be unregistered as well; you can register a teardown handler for this purpose:
```js
var sq = ASQ.react( function setup(next,registerTeardown){
var btn = document.getElementById( "mybtn" );
btn.addEventListener( "click", next, false );
// will be called once `sq.stop()` is called
registerTeardown( function(){
btn.removeEventListener( "click", next, false );
} );
} )
.seq( .. )
.then( .. )
.val( .. );
// later
sq.stop();
```
**Note:** The `this` binding reference inside the `setup(..)` handler is the same `sq` reactive sequence, so you can use the `this` reference to add to the reactive sequence definition, call methods like `stop()`, and so on.
Here's an example from the Node.js world, using reactive sequences to handle incoming HTTP requests:
```js
var server = http.createServer();
server.listen(8000);
// reactive observer
var request = ASQ.react( function setup(next,registerTeardown){
server.addListener( "request", next );
server.addListener( "close", this.stop );
registerTeardown( function(){
server.removeListener( "request", next );
server.removeListener( "close", request.stop );
} );
});
// respond to requests
request
.seq( pullFromDatabase )
.val( function(data,res){
res.end( data );
} );
// node teardown
process.on( "SIGINT", request.stop );
```
The `next(..)` trigger can also adapt to node streams easily, using `onStream(..)` and `unStream(..)`:
```js
ASQ.react( function setup(next){
var fstream = fs.createReadStream( "/some/file" );
// pipe the stream's "data" event to `next(..)`
next.onStream( fstream );
// listen for the end of the stream
fstream.on( "end", function(){
next.unStream( fstream );
} );
} )
.seq( .. )
.then( .. )
.val( .. );
```
You can also use sequence combinations to compose multiple reactive sequence streams:
```js
var sq1 = ASQ.react( .. ).seq( .. ).then( .. );
var sq2 = ASQ.react( .. ).seq( .. ).then( .. );
var sq3 = ASQ.react(..)
.gate(
sq1,
sq2
)
.then( .. );
```
The main takeaway is that `ASQ.react(..)` is a lightweight adaptation of F/RP concepts, enabling the wiring of an event stream to a sequence, hence the term "reactive sequence." Reactive sequences are generally capable enough for basic reactive uses.
**Note:** Here's an example of using `ASQ.react(..)` in managing UI state (http://jsbin.com/rozipaki/6/edit?js,output), and another example of handling HTTP request/response streams with `ASQ.react(..)` (https://gist.github.com/getify/bba5ec0de9d6047b720e).
## Generator Coroutine
Hopefully Chapter 4 helped you get pretty familiar with ES6 generators. In particular, we want to revisit the "Generator Concurrency" discussion, and push it even further.
We imagined a `runAll(..)` utility that could take two or more generators and run them concurrently, letting them cooperatively `yield` control from one to the next, with optional message passing.
In addition to being able to run a single generator to completion, the `ASQ#runner(..)` we discussed in Appendix A is a similar implementation of the concepts of `runAll(..)`, which can run multiple generators concurrently to completion.
So let's see how we can implement the concurrent Ajax scenario from Chapter 4:
```js
ASQ(
"http://some.url.2"
)
.runner(
function*(token){
// transfer control
yield token;
var url1 = token.messages[0]; // "http://some.url.1"
// clear out messages to start fresh
token.messages = [];
var p1 = request( url1 );
// transfer control
yield token;
token.messages.push( yield p1 );
},
function*(token){
var url2 = token.messages[0]; // "http://some.url.2"
// message pass and transfer control
token.messages[0] = "http://some.url.1";
yield token;
var p2 = request( url2 );
// transfer control
yield token;
token.messages.push( yield p2 );
// pass along results to next sequence step
return token.messages;
}
)
.val( function(res){
// `res[0]` comes from "http://some.url.1"
// `res[1]` comes from "http://some.url.2"
} );
```
The main differences between `ASQ#runner(..)` and `runAll(..)` are as follows:
* Each generator (coroutine) is provided an argument we call `token`, which is the special value to `yield` when you want to explicitly transfer control to the next coroutine.
* `token.messages` is an array that holds any messages passed in from the previous sequence step. It's also a data structure that you can use to share messages between coroutines.
* `yield`ing a Promise (or sequence) value does not transfer control, but instead pauses the coroutine processing until that value is ready.
* The last `return`ed or `yield`ed value from the coroutine processing run will be forward passed to the next step in the sequence.
It's also easy to layer helpers on top of the base `ASQ#runner(..)` functionality to suit different uses.
### State Machines
One example that may be familiar to many programmers is state machines. You can, with the help of a simple cosmetic utility, create an easy-to-express state machine processor.
Let's imagine such a utility. We'll call it `state(..)`, and will pass it two arguments: a state value and a generator that handles that state. `state(..)` will do the dirty work of creating and returning an adapter generator to pass to `ASQ#runner(..)`.
Consider:
```js
function state(val,handler) {
// make a coroutine handler for this state
return function*(token) {
// state transition handler
function transition(to) {
token.messages[0] = to;
}
// set initial state (if none set yet)
if (token.messages.length < 1) {
token.messages[0] = val;
}
// keep going until final state (false) is reached
while (token.messages[0] !== false) {
// current state matches this handler?
if (token.messages[0] === val) {
// delegate to state handler
yield *handler( transition );
}
// transfer control to another state handler?
if (token.messages[0] !== false) {
yield token;
}
}
};
}
```
If you look closely, you'll see that `state(..)` returns back a generator that accepts a `token`, and then it sets up a `while` loop that will run until the state machine reaches its final state (which we arbitrarily pick as the `false` value); that's exactly the kind of generator we want to pass to `ASQ#runner(..)`!
We also arbitrarily reserve the `token.messages[0]` slot as the place where the current state of our state machine will be tracked, which means we can even seed the initial state as the value passed in from the previous step in the sequence.
How do we use the `state(..)` helper along with `ASQ#runner(..)`?
```js
var prevState;
ASQ(
/* optional: initial state value */
2
)
// run our state machine
// transitions: 2 -> 3 -> 1 -> 3 -> false
.runner(
// state `1` handler
state( 1, function *stateOne(transition){
console.log( "in state 1" );
prevState = 1;
yield transition( 3 ); // goto state `3`
} ),
// state `2` handler
state( 2, function *stateTwo(transition){
console.log( "in state 2" );
prevState = 2;
yield transition( 3 ); // goto state `3`
} ),
// state `3` handler
state( 3, function *stateThree(transition){
console.log( "in state 3" );
if (prevState === 2) {
prevState = 3;
yield transition( 1 ); // goto state `1`
}
// all done!
else {
yield "That's all folks!";
prevState = 3;
yield transition( false ); // terminal state
}
} )
)
// state machine complete, so move on
.val( function(msg){
console.log( msg ); // That's all folks!
} );
```
It's important to note that the `*stateOne(..)`, `*stateTwo(..)`, and `*stateThree(..)` generators themselves are reinvoked each time that state is entered, and they finish when you `transition(..)` to another value. While not shown here, of course these state generator handlers can be asynchronously paused by `yield`ing Promises/sequences/thunks.
The underneath hidden generators produced by the `state(..)` helper and actually passed to `ASQ#runner(..)` are the ones that continue to run concurrently for the length of the state machine, and each of them handles cooperatively `yield`ing control to the next, and so on.
**Note:** See this "ping pong" example (http://jsbin.com/qutabu/1/edit?js,output) for more illustration of using cooperative concurrency with generators driven by `ASQ#runner(..)`.
## Communicating Sequential Processes (CSP)
"Communicating Sequential Processes" (CSP) was first described by C. A. R. Hoare in a 1978 academic paper (http://dl.acm.org/citation.cfm?doid=359576.359585), and later in a 1985 book (http://www.usingcsp.com/) of the same name. CSP describes a formal method for concurrent "processes" to interact (aka "communicate") during processing.
You may recall that we examined concurrent "processes" back in Chapter 1, so our exploration of CSP here will build upon that understanding.
Like most great concepts in computer science, CSP is heavily steeped in academic formalism, expressed as a process algebra. However, I suspect symbolic algebra theorems won't make much practical difference to the reader, so we will want to find some other way of wrapping our brains around CSP.
I will leave much of the formal description and proof of CSP to Hoare's writing, and to many other fantastic writings since. Instead, we will try to just briefly explain the idea of CSP in as un-academic and hopefully intuitively understandable a way as possible.
### Message Passing
The core principle in CSP is that all communication/interaction between otherwise independent processes must be through formal message passing. Perhaps counter to your expectations, CSP message passing is described as a synchronous action, where the sender process and the receiver process have to mutually be ready for the message to be passed.
How could such synchronous messaging possibly be related to asynchronous programming in JavaScript?
The concreteness of relationship comes from the nature of how ES6 generators are used to produce synchronous-looking actions that under the covers can indeed either be synchronous or (more likely) asynchronous.
In other words, two or more concurrently running generators can appear to synchronously message each other while preserving the fundamental asynchrony of the system because each generator's code is paused (aka "blocked") waiting on resumption of an asynchronous action.
How does this work?
Imagine a generator (aka "process") called "A" that wants to send a message to generator "B." First, "A" `yield`s the message (thus pausing "A") to be sent to "B." When "B" is ready and takes the message, "A" is then resumed (unblocked).
Symmetrically, imagine a generator "A" that wants a message **from** "B." "A" `yield`s its request (thus pausing "A") for the message from "B," and once "B" sends a message, "A" takes the message and is resumed.
One of the more popular expressions of this CSP message passing theory comes from ClojureScript's core.async library, and also from the *go* language. These takes on CSP embody the described communication semantics in a conduit that is opened between processes called a "channel."
**Note:** The term *channel* is used in part because there are modes in which more than one value can be sent at once into the "buffer" of the channel; this is similar to what you may think of as a stream. We won't go into depth about it here, but it can be a very powerful technique for managing streams of data.
In the simplest notion of CSP, a channel that we create between "A" and "B" would have a method called `take(..)` for blocking to receive a value, and a method called `put(..)` for blocking to send a value.
This might look like:
```js
var ch = channel();
function *foo() {
var msg = yield take( ch );
console.log( msg );
}
function *bar() {
yield put( ch, "Hello World" );
console.log( "message sent" );
}
run( foo );
run( bar );
// Hello World
// "message sent"
```
Compare this structured, synchronous(-looking) message passing interaction to the informal and unstructured message sharing that `ASQ#runner(..)` provides through the `token.messages` array and cooperative `yield`ing. In essence, `yield put(..)` is a single operation that both sends the value and pauses execution to transfer control, whereas in earlier examples we did those as separate steps.
Moreover, CSP stresses that you don't really explicitly "transfer control," but rather you design your concurrent routines to block expecting either a value received from the channel, or to block expecting to try to send a message on the channel. The blocking around receiving or sending messages is how you coordinate sequencing of behavior between the coroutines.
**Note:** Fair warning: this pattern is very powerful but it's also a little mind twisting to get used to at first. You will want to practice this a bit to get used to this new way of thinking about coordinating your concurrency.
There are several great libraries that have implemented this flavor of CSP in JavaScript, most notably "js-csp" (https://github.com/ubolonton/js-csp), which James Long (http://twitter.com/jlongster) forked (https://github.com/jlongster/js-csp) and has written extensively about (http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-in-JavaScript). Also, it cannot be stressed enough how amazing the many writings of David Nolen (http://twitter.com/swannodette) are on the topic of adapting ClojureScript's go-style core.async CSP into JS generators (http://swannodette.github.io/2013/08/24/es6-generators-and-csp/).
### asynquence CSP emulation
Because we've been discussing async patterns here in the context of my *asynquence* library, you might be interested to see that we can fairly easily add an emulation layer on top of `ASQ#runner(..)` generator handling as a nearly perfect porting of the CSP API and behavior. This emulation layer ships as an optional part of the "asynquence-contrib" package alongside *asynquence*.
Very similar to the `state(..)` helper from earlier, `ASQ.csp.go(..)` takes a generator -- in go/core.async terms, it's known as a goroutine -- and adapts it to use with `ASQ#runner(..)` by returning a new generator.
Instead of being passed a `token`, your goroutine receives an initially created channel (`ch` below) that all goroutines in this run will share. You can create more channels (which is often quite helpful!) with `ASQ.csp.chan(..)`.
In CSP, we model all asynchrony in terms of blocking on channel messages, rather than blocking waiting for a Promise/sequence/thunk to complete.
So, instead of `yield`ing the Promise returned from `request(..)`, `request(..)` should return a channel that you `take(..)` a value from. In other words, a single-value channel is roughly equivalent in this context/usage to a Promise/sequence.
Let's first make a channel-aware version of `request(..)`:
```js
function request(url) {
var ch = ASQ.csp.channel();
ajax( url ).then( function(content){
// `putAsync(..)` is a version of `put(..)` that
// can be used outside of a generator. It returns
// a promise for the operation's completion. We
// don't use that promise here, but we could if
// we needed to be notified when the value had
// been `take(..)`n.
ASQ.csp.putAsync( ch, content );
} );
return ch;
}
```
From Chapter 3, "promisory" is a Promise-producing utility, "thunkory" from Chapter 4 is a thunk-producing utility, and finally, in Appendix A we invented "sequory" for a sequence-producing utility.
Naturally, we need to coin a symmetric term here for a channel-producing utility. So let's unsurprisingly call it a "chanory" ("channel" + "factory"). As an exercise for the reader, try your hand at defining a `channelify(..)` utility similar to `Promise.wrap(..)`/`promisify(..)` (Chapter 3), `thunkify(..)` (Chapter 4), and `ASQ.wrap(..)` (Appendix A).
Now consider the concurrent Ajax example using *asyquence*-flavored CSP:
```js
ASQ()
.runner(
ASQ.csp.go( function*(ch){
yield ASQ.csp.put( ch, "http://some.url.2" );
var url1 = yield ASQ.csp.take( ch );
// "http://some.url.1"
var res1 = yield ASQ.csp.take( request( url1 ) );
yield ASQ.csp.put( ch, res1 );
} ),
ASQ.csp.go( function*(ch){
var url2 = yield ASQ.csp.take( ch );
// "http://some.url.2"
yield ASQ.csp.put( ch, "http://some.url.1" );
var res2 = yield ASQ.csp.take( request( url2 ) );
var res1 = yield ASQ.csp.take( ch );
// pass along results to next sequence step
ch.buffer_size = 2;
ASQ.csp.put( ch, res1 );
ASQ.csp.put( ch, res2 );
} )
)
.val( function(res1,res2){
// `res1` comes from "http://some.url.1"
// `res2` comes from "http://some.url.2"
} );
```
The message passing that trades the URL strings between the two goroutines is pretty straightforward. The first goroutine makes an Ajax request to the first URL, and that response is put onto the `ch` channel. The second goroutine makes an Ajax request to the second URL, then gets the first response `res1` off the `ch` channel. At that point, both responses `res1` and `res2` are completed and ready.
If there are any remaining values in the `ch` channel at the end of the goroutine run, they will be passed along to the next step in the sequence. So, to pass out message(s) from the final goroutine, `put(..)` them into `ch`. As shown, to avoid the blocking of those final `put(..)`s, we switch `ch` into buffering mode by setting its `buffer_size` to `2` (default: `0`).
**Note:** See many more examples of using *asynquence*-flavored CSP here (https://gist.github.com/getify/e0d04f1f5aa24b1947ae).
## Review
Promises and generators provide the foundational building blocks upon which we can build much more sophisticated and capable asynchrony.
*asynquence* has utilities for implementing *iterable sequences*, *reactive sequences* (aka "Observables"), *concurrent coroutines*, and even *CSP goroutines*.
Those patterns, combined with the continuation-callback and Promise capabilities, gives *asynquence* a powerful mix of different asynchronous functionalities, all integrated in one clean async flow control abstraction: the sequence.
================================================
FILE: async & performance/apC.md
================================================
# Вы не знаете JS: Асинхронность и Производительность
# Приложение C: Благодарности
Есть множество людей, которых нужно поблагодарить за то, что появилась на свет эта книга и вся серия.
Во-первых, я должен поблагодарить мою жену Кристен Симпсон (Christen Simpson) и двух моих детей Итана (Ethan) и Эмили (Emily), за то, что мирились с тем, что их папу вечно надо было отрывать от компьютера. Даже когда я не писал книги, моя одержимость JavaScript приклеивала мой взгляд к экрану больше, чем следовало. То время, которое я занял у моей семьи, и есть причина, по которой эти книги могут так глубоко и полностью объяснить JavaScript для вас, читатель. Я в большом долгу перед своей семьей.
Хочу поблагодарить моих редакторов в O'Reilly, а именно Simon St.Laurent и Brian MacDonald, как и остальных в команде редакторов и маркетинга. Работать с ними — одно удовольствие, и хочется особенно поблагодарить их за создание подходящих условий во время этого эксперимента с написанием "open source"-книги, за редактирование и выпуск.
Спасибо всем тем, кто участвовал в улучшении этой серии книг присылая предложения по редактированию и корректировке, включая Shelley Powers, Tim Ferro, Evan Borden, Forrest L. Norvell, Jennifer Davis, Jesse Harlin и многих других. Большое спасибо Shane Hudson за написание предисловия к этой серии книг.
Спасибо бесчисленным участникам сообщества, включая членов комитета TC39, кто поделился столько многим с нами и особенно за терпеливое отношение к моим бесконечным вопросам и изысканиям. John-David Dalton, Juriy "kangax" Zaytsev, Mathias Bynens, Axel Rauschmayer, Nicholas Zakas, Angus Croll, Reginald Braithwaite, Dave Herman, Brendan Eich, Allen Wirfs-Brock, Bradley Meck, Domenic Denicola, David Walsh, Tim Disney, Peter van der Zee, Andrea Giammarchi, Kit Cambridge, Eric Elliott и многие другие, я упомянул лишь малую часть.
Серия книг *Вы не знаете JS* родилась на Kickstarter, поэтому я также хочу поблагодарить всех моих (почти) 500 щедрых инвесторов, без которых эта серия книг не появилась бы:
> Jan Szpila, nokiko, Murali Krishnamoorthy, Ryan Joy, Craig Patchett, pdqtrader, Dale Fukami, ray hatfield, R0drigo Perez [Mx], Dan Petitt, Jack Franklin, Andrew Berry, Brian Grinstead, Rob Sutherland, Sergi Meseguer, Phillip Gourley, Mark Watson, Jeff Carouth, Alfredo Sumaran, Martin Sachse, Marcio Barrios, Dan, AimelyneM, Matt Sullivan, Delnatte Pierre-Antoine, Jake Smith, Eugen Tudorancea, Iris, David Trinh, simonstl, Ray Daly, Uros Gruber, Justin Myers, Shai Zonis, Mom & Dad, Devin Clark, Dennis Palmer, Brian Panahi Johnson, Josh Marshall, Marshall, Dennis Kerr, Matt Steele, Erik Slagter, Sacah, Justin Rainbow, Christian Nilsson, Delapouite, D.Pereira, Nicolas Hoizey, George V. Reilly, Dan Reeves, Bruno Laturner, Chad Jennings, Shane King, Jeremiah Lee Cohick, od3n, Stan Yamane, Marko Vucinic, Jim B, Stephen Collins, Ægir Þorsteinsson, Eric Pederson, Owain, Nathan Smith, Jeanetteurphy, Alexandre ELISÉ, Chris Peterson, Rik Watson, Luke Matthews, Justin Lowery, Morten Nielsen, Vernon Kesner, Chetan Shenoy, Paul Tregoing, Marc Grabanski, Dion Almaer, Andrew Sullivan, Keith Elsass, Tom Burke, Brian Ashenfelter, David Stuart, Karl Swedberg, Graeme, Brandon Hays, John Christopher, Gior, manoj reddy, Chad Smith, Jared Harbour, Minoru TODA, Chris Wigley, Daniel Mee, Mike, Handyface, Alex Jahraus, Carl Furrow, Rob Foulkrod, Max Shishkin, Leigh Penny Jr., Robert Ferguson, Mike van Hoenselaar, Hasse Schougaard, rajan venkataguru, Jeff Adams, Trae Robbins, Rolf Langenhuijzen, Jorge Antunes, Alex Koloskov, Hugh Greenish, Tim Jones, Jose Ochoa, Michael Brennan-White, Naga Harish Muvva, Barkóczi Dávid, Kitt Hodsden, Paul McGraw, Sascha Goldhofer, Andrew Metcalf, Markus Krogh, Michael Mathews, Matt Jared, Juanfran, Georgie Kirschner, Kenny Lee, Ted Zhang, Amit Pahwa, Inbal Sinai, Dan Raine, Schabse Laks, Michael Tervoort, Alexandre Abreu, Alan Joseph Williams, NicolasD, Cindy Wong, Reg Braithwaite, LocalPCGuy, Jon Friskics, Chris Merriman, John Pena, Jacob Katz, Sue Lockwood, Magnus Johansson, Jeremy Crapsey, Grzegorz Pawłowski, nico nuzzaci, Christine Wilks, Hans Bergren, charles montgomery, Ariel בר-לבב Fogel, Ivan Kolev, Daniel Campos, Hugh Wood, Christian Bradford, Frédéric Harper, Ionuţ Dan Popa, Jeff Trimble, Rupert Wood, Trey Carrico, Pancho Lopez, Joël kuijten, Tom A Marra, Jeff Jewiss, Jacob Rios, Paolo Di Stefano, Soledad Penades, Chris Gerber, Andrey Dolganov, Wil Moore III, Thomas Martineau, Kareem, Ben Thouret, Udi Nir, Morgan Laupies, jory carson-burson, Nathan L Smith, Eric Damon Walters, Derry Lozano-Hoyland, Geoffrey Wiseman, mkeehner, KatieK, Scott MacFarlane, Brian LaShomb, Adrien Mas, christopher ross, Ian Littman, Dan Atkinson, Elliot Jobe, Nick Dozier, Peter Wooley, John Hoover, dan, Martin A. Jackson, Héctor Fernando Hurtado, andy ennamorato, Paul Seltmann, Melissa Gore, Dave Pollard, Jack Smith, Philip Da Silva, Guy Israeli, @megalithic, Damian Crawford, Felix Gliesche, April Carter Grant, Heidi, jim tierney, Andrea Giammarchi, Nico Vignola, Don Jones, Chris Hartjes, Alex Howes, john gibbon, David J. Groom, BBox, Yu 'Dilys' Sun, Nate Steiner, Brandon Satrom, Brian Wyant, Wesley Hales, Ian Pouncey, Timothy Kevin Oxley, George Terezakis, sanjay raj, Jordan Harband, Marko McLion, Wolfgang Kaufmann, Pascal Peuckert, Dave Nugent, Markus Liebelt, Welling Guzman, Nick Cooley, Daniel Mesquita, Robert Syvarth, Chris Coyier, Rémy Bach, Adam Dougal, Alistair Duggin, David Loidolt, Ed Richer, Brian Chenault, GoldFire Studios, Carles Andrés, Carlos Cabo, Yuya Saito, roberto ricardo, Barnett Klane, Mike Moore, Kevin Marx, Justin Love, Joe Taylor, Paul Dijou, Michael Kohler, Rob Cassie, Mike Tierney, Cody Leroy Lindley, tofuji, Shimon Schwartz, Raymond, Luc De Brouwer, David Hayes, Rhys Brett-Bowen, Dmitry, Aziz Khoury, Dean, Scott Tolinski - Level Up, Clement Boirie, Djordje Lukic, Anton Kotenko, Rafael Corral, Philip Hurwitz, Jonathan Pidgeon, Jason Campbell, Joseph C., SwiftOne, Jan Hohner, Derick Bailey, getify, Daniel Cousineau, Chris Charlton, Eric Turner, David Turner, Joël Galeran, Dharma Vagabond, adam, Dirk van Bergen, dave ♥♫★ furf, Vedran Zakanj, Ryan McAllen, Natalie Patrice Tucker, Eric J. Bivona, Adam Spooner, Aaron Cavano, Kelly Packer, Eric J, Martin Drenovac, Emilis, Michael Pelikan, Scott F. Walter, Josh Freeman, Brandon Hudgeons, vijay chennupati, Bill Glennon, Robin R., Troy Forster, otaku_coder, Brad, Scott, Frederick Ostrander, Adam Brill, Seb Flippence, Michael Anderson, Jacob, Adam Randlett, Standard, Joshua Clanton, Sebastian Kouba, Chris Deck, SwordFire, Hannes Papenberg, Richard Woeber, hnzz, Rob Crowther, Jedidiah Broadbent, Sergey Chernyshev, Jay-Ar Jamon, Ben Combee, luciano bonachela, Mark Tomlinson, Kit Cambridge, Michael Melgares, Jacob Adams, Adrian Bruinhout, Bev Wieber, Scott Puleo, Thomas Herzog, April Leone, Daniel Mizieliński, Kees van Ginkel, Jon Abrams, Erwin Heiser, Avi Laviad, David newell, Jean-Francois Turcot, Niko Roberts, Erik Dana, Charles Neill, Aaron Holmes, Grzegorz Ziółkowski, Nathan Youngman, Timothy, Jacob Mather, Michael Allan, Mohit Seth, Ryan Ewing, Benjamin Van Treese, Marcelo Santos, Denis Wolf, Phil Keys, Chris Yung, Timo Tijhof, Martin Lekvall, Agendine, Greg Whitworth, Helen Humphrey, Dougal Campbell, Johannes Harth, Bruno Girin, Brian Hough, Darren Newton, Craig McPheat, Olivier Tille, Dennis Roethig, Mathias Bynens, Brendan Stromberger, sundeep, John Meyer, Ron Male, John F Croston III, gigante, Carl Bergenhem, B.J. May, Rebekah Tyler, Ted Foxberry, Jordan Reese, Terry Suitor, afeliz, Tom Kiefer, Darragh Duffy, Kevin Vanderbeken, Andy Pearson, Simon Mac Donald, Abid Din, Chris Joel, Tomas Theunissen, David Dick, Paul Grock, Brandon Wood, John Weis, dgrebb, Nick Jenkins, Chuck Lane, Johnny Megahan, marzsman, Tatu Tamminen, Geoffrey Knauth, Alexander Tarmolov, Jeremy Tymes, Chad Auld, Sean Parmelee, Rob Staenke, Dan Bender, Yannick derwa, Joshua Jones, Geert Plaisier, Tom LeZotte, Christen Simpson, Stefan Bruvik, Justin Falcone, Carlos Santana, Michael Weiss, Pablo Villoslada, Peter deHaan, Dimitris Iliopoulos, seyDoggy, Adam Jordens, Noah Kantrowitz, Amol M, Matthew Winnard, Dirk Ginader, Phinam Bui, David Rapson, Andrew Baxter, Florian Bougel, Michael George, Alban Escalier, Daniel Sellers, Sasha Rudan, John Green, Robert Kowalski, David I. Teixeira (@ditma, Charles Carpenter, Justin Yost, Sam S, Denis Ciccale, Kevin Sheurs, Yannick Croissant, Pau Fracés, Stephen McGowan, Shawn Searcy, Chris Ruppel, Kevin Lamping, Jessica Campbell, Christopher Schmitt, Sablons, Jonathan Reisdorf, Bunni Gek, Teddy Huff, Michael Mullany, Michael Fürstenberg, Carl Henderson, Rick Yoesting, Scott Nichols, Hernán Ciudad, Andrew Maier, Mike Stapp, Jesse Shawl, Sérgio Lopes, jsulak, Shawn Price, Joel Clermont, Chris Ridmann, Sean Timm, Jason Finch, Aiden Montgomery, Elijah Manor, Derek Gathright, Jesse Harlin, Dillon Curry, Courtney Myers, Diego Cadenas, Arne de Bree, João Paulo Dubas, James Taylor, Philipp Kraeutli, Mihai Păun, Sam Gharegozlou, joshjs, Matt Murchison, Eric Windham, Timo Behrmann, Andrew Hall, joshua price, Théophile Villard
Эта серия книг выпускается в стиле "open source", включая редактирование и выпуск. Мы отдаем дань благодарности GitHub за предоставление такой возможности для сообщества!
Еще раз спасибо всех бесчисленным людям, которых я не перечислил по имени, но кого я тем не менее должен поблагодарить. Пусть эта серия книг будет "принадлежать" всем нам и служить вкладом в увеличение информированности и понимании языка JavaScript, на благо все нынешних и будущих вкладчиков в общее дело сообщества.
================================================
FILE: async & performance/ch1.md
================================================
# Вы не знаете JS: Асинхронность и Выполнение
# Глава 1: Асинхронность: Сейчас и Потом
Управление программой в течении её времени выполнения является глубокой и, в то же время, важной темой для понимания Javascript.
Имеется ввиду не только то, что происходит от начала 'for' цикла и до его завершения, выполнение которого занимает, в основном, небольшой промежуток времени (от микро до миллисекунд), но и, то, что случается между запуском одной части программы *сейчас* и другой *позже* -- в этом промежутке, когда она не в активном исполнении.
Практически, все нетривиальные программы (особенно, написанные на Javascript) управляют выше упомянутым промежутком, будь то ожидание ввода данных пользователем, запрос информации с базы данных или файловой системы, отправка данных по сети и ожидание ответа, или выполнение повторяющихся действий в заданном интервале (анимация). Во всех этих случаях, ваша программа должна управлять своим состоянием во время этой "паузы" в ходе своего исполнения. Как говорят в Лондоне "Mind the gap!"(дословно -- "Помни о разрыве") -- надпись на платформе, предупреждающая о расстоянии между её краем и дверью поезда.
На самом деле, отношение *сейчас* и *потом* частей вашей программы является сердцем асинхронного программирования.
Асинхронное программирование существовало в JS изначально. Но многие разработчики не вдавались в подробности как именно оно работает в их программах, или находили *другие* способы управления им. *Почти хорошим* подходом считалась скромная callback функция. Многие до сих пор считают, что callback'и являются эффективным решением.
Но с ростом масштаба и сложности JS, для удовлетворения постоянно расширяющихся требований первоклассного языка программирования, работающего как в браузерах так и на серверах, и на любых мыслимых устройствах, между прочим, росла и боль с которой приходилось управлять асинхронностью.
Сейчас это все может показаться довольно абстрактным. Но я вас уверяю, что мы углубимся в детали и изучим множество новых методов для асинхронного программирования в JavaScript в следующих главах.
Для этого нам надо разобраться во всех тонкостях асинхронности в JS.
## Программа по кусочкам
Даже, если вы пишете ваш код в одном *.js* файле, ваша программа почти наверняка состоит из нескольких *кусочков*, один из которых выполнится *сейчас*, а остальные *потом*. Наиболее распространенная единица *кусочка* - 'function'.
Большинство новоприбывших в JS разработчиков думают, что асинхронный код приостанавливается для выполнения, но это не так.
Давайте посмотрим:
```js
// ajax(..) обычная ajax функция
var data = ajax( "http://some.url.1" );
console.log( data );
// Упс! `данные` не получены
```
Вы вероятно знаете, что Ajax запросы не выполняются синхронно, а это значит, что `ajax(..)` функция ещё не получила данных для присвоения в переменную `data`. Если бы `ajax(..)` запрос мог бы приостановить код для получения данных, тогда бы `data` не была пуста.
Простейшая (но далеко не самая лучшая) реализация Ajax -- это использование функции callback.
```js
ajax( "http://some.url.1", function myCallbackFunction(data){
console.log( data ); // Ура, Я получил `data`!
} );
```
**Предупреждение.** Возможно, вы слышали, что можно выполнять синхронные запросы Ajax. Хотя технически это верно, вы никогда не должны этого делать ни при каких обстоятельствах, потому что это блокирует пользовательский интерфейс браузера (кнопки, меню, прокрутку и т.д.) и предотвращает любое взаимодействие с пользователем. Это ужасная идея, и ее всегда следует избегать.
Прежде чем возражать против несогласия, нет, ваше желание избежать путаницы с колбеками *не* является оправданием для блокировки синхронного Ajax.
Например, рассмотрим этот код:
```js
function now() {
return 21;
}
function later() {
answer = answer * 2;
console.log( "Meaning of life:", answer );
}
var answer = now();
setTimeout( later, 1000 ); // Meaning of life: 42
```
В этой программе есть две части: то, что будет запущено *сейчас*, и то, что будет запущено *позже*. Должно быть довольно очевидно, что это за два фрагмента, но давайте будем очень явными:
Сейчас:
```js
function now() {
return 21;
}
function later() { .. }
var answer = now();
setTimeout( later, 1000 );
```
Позже:
```js
answer = answer * 2;
console.log( "Meaning of life:", answer );
```
Блок *now* запускается сразу же, как только вы запускаете свою программу. Но `setTimeout(..)` также устанавливает событие (тайм-аут), которое произойдет *позже*, поэтому содержимое функции `later()` будет выполнено позже (через 1000 миллисекунд).
Каждый раз, когда вы заключаете часть кода в `функцию` и указываете, что она должна выполняться в ответ на какое-то событие (таймер, щелчок мышью, ответ Ajax и т.д.), вы создаете более позднюю* часть своего кода, и, таким образом, вводя асинхронность в вашу программу.
### Асинхронная консоль
Не существует спецификации или набора требований относительно того, как работают методы `console.*` — они официально не являются частью JavaScript, а вместо этого добавляются в JS *средой всплытия* (см. заголовок *Типы и грамматика* эту серию книг).
Таким образом, разные браузеры и среды JS делают то, что им заблагорассудится, что иногда может привести к путанице.
В частности, есть некоторые браузеры и некоторые условия, при которых `console.log(..)` на самом деле не сразу выводит то, что ему дано. Основная причина, по которой это может произойти, заключается в том, что ввод-вывод является очень медленной и блокирующей частью многих программ (не только JS). Таким образом, браузер может лучше (с точки зрения страницы/интерфейса) обрабатывать «консольный» ввод-вывод асинхронно в фоновом режиме, и вы, возможно, даже не подозреваете об этом.
Не очень распространенный, но возможный сценарий, в котором это может быть *наблюдаемо* (не из самого кода, а извне):
```js
var a = {
index: 1
};
// позже
console.log( a ); // ??
// ещё позже
a.index++;
```
Обычно мы ожидаем, что снимок объекта `a` будет сделан точно в момент выполнения оператора `console.log(..)`, выводя что-то вроде `{ index: 1 }`, так что в следующем операторе, когда `a.index++`, он изменяет что-то отличное от вывода `a` или сразу после него.
В большинстве случаев приведенный выше код, вероятно, будет создавать представление объекта в консоли инструментов разработчика, которое вы ожидаете. Но возможно, что этот же код может работать в ситуации, когда браузер считает необходимым отложить консольный ввод-вывод в фоновом режиме, и в этом случае *возможно*, что к тому времени, когда объект будет представлен в консоли браузера, ` a.index++` уже произошло, и он показывает `{ index: 2 }`.
Это движущаяся цель, при каких условиях именно «консольный» ввод-вывод будет отложен, или даже будет ли он наблюдаться. Просто имейте в виду эту возможную асинхронность в вводе/выводе на тот случай, если вы когда-нибудь столкнетесь с проблемами при отладке, когда объекты были изменены *после* оператора `console.log(..)`, и тем не менее вы видите неожиданные изменения.
**Примечание.** Если вы столкнулись с этим редким случаем, лучше всего использовать точки останова в отладчике JS вместо того, чтобы полагаться на вывод консоли. Следующим лучшим вариантом было бы принудительно сделать «моментальный снимок» рассматриваемого объекта, сериализовав его в «строку», например, с «JSON.stringify(..)».
## Цикл событий (Event Loop)
Давайте сделаем (возможно, шокирующее) утверждение: несмотря на явное разрешение асинхронного кода JS (например, тайм-аут, который мы только что рассмотрели), до недавнего времени (ES6) сам JavaScript на самом деле никогда не имел прямого встроенного понятия асинхронности.
**Что!?** Это кажется безумием, верно? На самом деле, это правда. Сам JS-движок никогда не делал ничего, кроме как выполнял один фрагмент вашей программы в любой момент, когда его об этом попросят.
"Попросят" Кто? Это важная часть!
Движок JS не работает изолированно. Он работает внутри *среды хостинга*, которая для большинства разработчиков является типичным веб-браузером. За последние несколько лет (но не исключительно) JS расширился за пределы браузера в другие среды, такие как серверы, с помощью таких вещей, как Node.js. Фактически, в наши дни JavaScript внедряется во все виды устройств, от роботов до лампочек.
Но один общий «поток» (это не очень тонкая асинхронная шутка, чего бы это ни стоило) всех этих сред заключается в том, что в них есть механизм, который обрабатывает выполнение нескольких фрагментов вашей программы *с течением времени*, в каждый момент времени. момент вызова JS-движка, называемый «циклом событий».
Другими словами, движок JS не имеет врожденного чувства *времени*, а вместо этого является средой выполнения по требованию для любого произвольного фрагмента JS. Это окружающая среда, которая всегда *планирует* «события» (выполнения кода JS).
Так, например, когда ваша программа JS делает запрос Ajax для получения некоторых данных с сервера, вы настраиваете код «ответа» в функции (обычно называемой «обратным вызовом»), и механизм JS сообщает среде хостинга, «Эй, я собираюсь приостановить выполнение на данный момент, но когда вы закончите с этим сетевым запросом, и у вас есть какие-то данные, *вызовите* эту функцию *назад*».
Затем браузер настраивается на прослушивание ответа из сети, и когда ему есть что вам дать, он планирует выполнение функции обратного вызова, вставляя ее в *цикл событий*.
Так что же такое *цикл событий*?
Давайте сначала концептуализируем это с помощью некоторого фальшивого кода:
```js
// `eventLoop` - это массив, который действует как очередь (первым пришел, первым ушел)
var eventLoop = [ ];
var event;
// будет работать "всегда"
while (true) {
// выполняем "такт"
if (eventLoop.length > 0) {
// получаем следующее событие из очереди
event = eventLoop.shift();
// теперь выполняем следующее событие
try {
event();
}
catch (err) {
reportError(err);
}
}
}
```
Это, конечно, сильно упрощенный псевдокод для иллюстрации концепций. Но этого должно быть достаточно, чтобы помочь лучше понять.
Как видите, существует непрерывно работающий цикл, представленный циклом while, и каждая итерация этого цикла называется «тактом». Для каждого такта, если событие ожидает в очереди, оно снимается и выполняется. Эти события являются обратными вызовами ваших функций.
Важно отметить, что `setTimeout(..)` не помещает ваш обратный вызов в очередь цикла событий. Что он делает, так это устанавливает таймер; когда таймер истекает, среда помещает ваш обратный вызов в цикл событий, так что какой-то будущий такт подберет его и выполнит.
Что делать, если в этот момент в цикле событий уже 20 элементов? Ваш обратный вызов ждет. Он становится в очередь позади других — обычно нет пути для опережения очереди и пропуска вперед в очереди. Это объясняет, почему таймеры `setTimeout(..)` могут не срабатывать с идеальной временной точностью. Вам гарантируется (грубо говоря), что ваш обратный вызов не сработает *до* указанного вами временного интервала, но это может произойти в это время или позже, в зависимости от состояния очереди событий.
Таким образом, другими словами, ваша программа обычно разбивается на множество небольших фрагментов, которые выполняются один за другим в очереди цикла событий. И технически, другие события, не связанные напрямую с вашей программой, также могут чередоваться в очереди.
**Примечание.** Мы упоминали «до недавнего времени» в связи с тем, что ES6 изменил характер управления очередью цикла событий. В основном это формальная техническая особенность, но теперь ES6 определяет, как работает цикл обработки событий, что означает, что технически это находится в компетенции JS-движка, а не только *среды хостинга*. Одной из основных причин этого изменения является введение промисов ES6, которые мы обсудим в главе 3, потому что они требуют возможности иметь прямой, детальный контроль над операциями планирования в очереди цикла событий (см. обсуждение `setTimeout (..0)` в разделе "Cooperation").
## Параллельная обработка
Очень часто смешивают термины «асинхронный» и «параллельный», но на самом деле они совершенно разные. Помните, что асинхронность — это разрыв между *сейчас* и *позже*. Но параллель означает, что вещи могут происходить одновременно.
Наиболее распространенными инструментами для параллельных вычислений являются процессы и потоки. Процессы и потоки выполняются независимо и могут выполняться одновременно: на разных процессорах или даже на разных компьютерах, но несколько потоков могут совместно использовать память одного процесса.
Цикл событий, напротив, разбивает свою работу на задачи и выполняет их последовательно, запрещая параллельный доступ и изменения в разделяемой памяти. Параллелизм и «сериализм» могут сосуществовать в виде взаимодействующих циклов событий в отдельных потоках.
Чередование параллельных потоков выполнения и чередование асинхронных событий происходит на очень разных уровнях детализации.
Например:
```js
function later() {
answer = answer * 2;
console.log( "Meaning of life:", answer );
}
```
В то время как все содержимое `later()` будет рассматриваться как одна запись в очереди цикла событий, если подумать о потоке, в котором будет выполняться этот код, на самом деле существует, возможно, дюжина различных низкоуровневых операций. Например, `answer = answer * 2` требует сначала загрузить текущее значение `answer`, затем куда-то поместить `2`, затем выполнить умножение, затем взять результат и сохранить его обратно в `answer`.
В однопоточной среде на самом деле не имеет значения, что элементы в очереди потоков являются низкоуровневыми операциями, потому что ничто не может прервать поток. Но если у вас есть параллельная система, в которой два разных потока работают в одной и той же программе, вы, скорее всего, получите непредсказуемое поведение.
Рассмотрим:
```js
var a = 20;
function foo() {
a = a + 1;
}
function bar() {
a = a * 2;
}
// ajax(..) — это произвольная функция Ajax, заданная библиотекой
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
```
В однопоточном поведении JavaScript, если `foo()` запускается до `bar()`, результатом будет то, что `a` имеет `42`, но если `bar()` запускается до `foo()`, результатом будет `a` будет `41`.
Если бы события JS, совместно использующие одни и те же данные, выполнялись параллельно, проблемы были бы гораздо более тонкими. Рассмотрим эти два списка задач псевдокода как потоки, которые могли бы соответственно запускать код в `foo()` и `bar()`, и подумайте, что произойдет, если они будут выполняться точно в одно и то же время:
Поток 1 («X» и «Y» — временные ячейки памяти):
```
foo():
а. загрузить значение `a` в `X`
б. сохранить `1` в `Y`
в. добавить `X` и `Y`, сохранить результат в `X`
д. сохранить значение `X` в `a`
```
Поток 2 («X» и «Y» — временные ячейки памяти):
```
bar():
а. загрузить значение `a` в `X`
б. сохранить `2` в `Y`
в. умножить `X` и `Y`, сохранить результат в `X`
д. сохранить значение `X` в `a`
```
Теперь предположим, что два потока действительно выполняются параллельно. Вы, вероятно, можете определить проблему, верно? Они используют разделяемые ячейки памяти `X` и `Y` для своих временных шагов.
Каков конечный результат в `a`, если шаги происходят так?
```
1a (загрузить значение `a` в `X` ==> `20`)
2a (загрузить значение `a` в `X` ==> `20`)
1b (сохранить `1` в `Y` ==> `1`)
2b (сохранить `2` в `Y` ==> `2`)
1c (добавьте `X` и `Y`, сохраните результат в `X` ==> `22`)
1d (сохранить значение `X` в `a` ==> `22`)
2c (умножить `X` и `Y`, сохранить результат в `X` ==> `44`)
2d (сохранить значение `X` в `a` ==> `44`)
```
Результатом в «а» будет «44». Но как быть с этим заказом?
```
1a (загрузить значение `a` в `X` ==> `20`)
2a (загрузить значение `a` в `X` ==> `20`)
2b (сохранить `2` в `Y` ==> `2`)
1b (сохранить `1` в `Y` ==> `1`)
2c (умножить `X` и `Y`, сохранить результат в `X` ==> `20`)
1c (добавьте `X` и `Y`, сохраните результат в `X` ==> `21`)
1d (сохранить значение `X` в `a` ==> `21`)
2d (сохранить значение `X` в `a` ==> `21`)
```
Результатом в «а» будет «21».
Таким образом, многопоточное программирование очень сложно, потому что, если вы не предпримете специальных шагов для предотвращения такого прерывания/перемежения, вы можете получить очень неожиданное, недетерминированное поведение, которое часто приводит к головной боли.
JavaScript никогда не делится данными между потоками, а это означает, что *этот* уровень недетерминизма не имеет значения. Но это не значит, что JS всегда детерминирован. Помните ранее, где относительное упорядочение `foo()` и `bar()` дает два разных результата (`41` или `42`)?
**Примечание.** Возможно, это еще не очевидно, но не всякий недетерминизм плох. Иногда это неуместно, а иногда намеренно. Мы увидим больше примеров этого в этой и следующих нескольких главах.
### Выполнить до завершения
Из-за однопоточности JavaScript код внутри `foo()` (и `bar()`) является атомарным, что означает, что как только `foo()` запускается, весь его код завершится до того, как любой из кода в `bar()` начнет работать, и наоборот. Это называется поведением «запуск до завершения».
На самом деле семантика выполнения до завершения более очевидна, когда `foo()` и `bar()` содержат больше кода, например:
```js
var a = 1;
var b = 2;
function foo() {
a++;
b = b * a;
a = b + 3;
}
function bar() {
b--;
a = 8 + b;
b = a * 2;
}
// ajax(..) — это произвольная функция Ajax, заданная библиотекой
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
```
Поскольку `foo()` не может быть прервана `bar()`, а `bar()` не может быть прервана `foo()`, эта программа имеет только два возможных результата в зависимости от того, какой из них запустится первым: - если бы существовала многопоточность и отдельные операторы в `foo()` и `bar()` могли чередоваться, количество возможных результатов было бы значительно увеличено!
Блок 1 является синхронным (происходит *сейчас*), но фрагменты 2 и 3 асинхронны (происходят *позже*), что означает, что их выполнение будет разделено временным промежутком.
Блок 1:
```js
var a = 1;
var b = 2;
```
Блок 2 (`foo()`):
```js
a++;
b = b * a;
a = b + 3;
```
Блок 3 (`bar()`):
```js
b--;
a = 8 + b;
b = a * 2;
```
Блоки 2 и 3 могут выполняться в любом порядке, поэтому для этой программы есть два возможных результата, как показано здесь:
Вывод 1:
```js
var a = 1;
var b = 2;
// foo()
a++;
b = b * a;
a = b + 3;
// bar()
b--;
a = 8 + b;
b = a * 2;
a; // 11
b; // 22
```
Вывод 2:
```js
var a = 1;
var b = 2;
// bar()
b--;
a = 8 + b;
b = a * 2;
// foo()
a++;
b = b * a;
a = b + 3;
a; // 183
b; // 180
```
Два результата одного и того же кода означают, что у нас все еще есть недетерминизм! Но это на уровне упорядочения функций (событий), а не на уровне упорядочения операторов (или, фактически, на уровне упорядочения операций выражений), как в случае с потоками. Другими словами, это *более детерминировано*, чем потоки.
Применительно к поведению JavaScript этот недетерминизм упорядочения функций является общим термином «состояние гонки», поскольку `foo()` и `bar()` соревнуются друг с другом, чтобы увидеть, какой из них запустится первым. В частности, это «состояние гонки», потому что вы не можете надежно предсказать, как окажутся «a» и «b».
**Примечание:** Если бы в JS была функция, которая каким-то образом не имела бы поведения выполнения до завершения, у нас могло бы быть гораздо больше возможных результатов, верно? Оказывается, ES6 представляет именно такую вещь (см. главу 4 «Генераторы»), но не волнуйтесь прямо сейчас, мы еще вернемся к этому!
## Конкурентность
Давайте представим себе сайт, на котором отображается список обновлений статуса (например, лента новостей в социальной сети), который постепенно загружается по мере того, как пользователь прокручивает список вниз. Чтобы такая функция работала правильно, (по крайней мере) два отдельных «процесса» должны выполняться *одновременно* (т.е. в течение одного и того же промежутка времени, но не обязательно в один и тот же момент).
**Примечание.** Мы используем здесь слово «процесс» в кавычках, потому что это не настоящие процессы уровня операционной системы с точки зрения информатики. Это виртуальные процессы или задачи, которые представляют собой логически связанные последовательные серии операций. Мы просто предпочтем «процесс», а не «задачу», потому что с точки зрения терминологии это будет соответствовать определениям изучаемых нами понятий.
Первый «процесс» будет реагировать на события «onscroll» (запросы Ajax для нового контента), поскольку они срабатывают, когда пользователь прокручивает страницу дальше вниз. Второй «процесс» будет получать ответы Ajax (для отображения содержимого на странице).
Очевидно, что если пользователь прокручивает страницу достаточно быстро, вы можете увидеть два или более события onscroll, запускаемых в течение времени, необходимого для получения и обработки первого ответа, и, таким образом, у вас будут события onscroll и события ответа Ajax, которые будут **конкурировать** друг с другом.
Конкурентность — это когда два или более «процессов» выполняются одновременно в течение одного и того же периода, независимо от того, происходят ли их отдельные составляющие операции *параллельно* (в один и тот же момент на разных процессорах или ядрах) или нет. Тогда вы можете думать о конкурентности как о параллелизме на уровне «процесса» (или на уровне задачи), в отличие от параллелизма на уровне операций (потоки, исполняемые на отдельных процессорах).
**Примечание.** Конкурентность также вводит необязательное понятие этих «процессов», взаимодействующих друг с другом. Мы вернемся к этому позже.
Для данного окна времени (несколько секунд пользовательской прокрутки) давайте визуализируем каждый независимый «процесс» как серию событий/операций:
"Процесс" 1 (`onscroll` events):
```
onscroll, request 1
onscroll, request 2
onscroll, request 3
onscroll, request 4
onscroll, request 5
onscroll, request 6
onscroll, request 7
```
"Процесс" 2 (Ajax response events):
```
response 1
response 2
response 3
response 4
response 5
response 6
response 7
```
Вполне возможно, что событие `onscroll` и событие ответа Ajax могут быть готовы к обработке в один и тот же *момент*. Например, давайте визуализируем эти события на временной шкале:
```
onscroll, request 1
onscroll, request 2 response 1
onscroll, request 3 response 2
response 3
onscroll, request 4
onscroll, request 5
onscroll, request 6 response 4
onscroll, request 7
response 6
response 5
response 7
```
Но, возвращаясь к нашему представлению о цикле событий из предыдущей главы, JS сможет обрабатывать только одно событие за раз, поэтому либо `onscroll, request 2` будет происходить первым, либо `response 1` должно произойти первым, но они не могут произойти буквально в один и тот же момент. Так же, как дети в школьной столовой, независимо от того, какую толпу они образуют за дверью, им придется выстроиться в одну очередь, чтобы получить свой обед!
Давайте визуализируем чередование всех этих событий в очереди цикла событий.
Очередь цикла событий:
```
onscroll, request 1 <--- Процесс 1 начинается
onscroll, request 2
response 1 <--- Процесс 2 начинается
onscroll, request 3
response 2
response 3
onscroll, request 4
onscroll, request 5
onscroll, request 6
response 4
onscroll, request 7 <--- Процесс 1 заканчивается
response 6
response 5
response 7 <--- Процесс 2 заканчивается
```
«Процесс 1» и «Процесс 2» выполняются одновременно (параллельно на уровне задач), но их отдельные события выполняются последовательно в очереди цикла событий.
Кстати, обратите внимание, как «ответ 6» и «ответ 5» вернулись не в том порядке, в котором ожидалось?
Однопоточный цикл событий — это одно из проявлений конкурентности (конечно, есть и другие, к которым мы вернемся позже).
### Не взаимодействующий
Поскольку два или более «процессов» одновременно чередуют свои шаги/события одновременно внутри одной программы, им не обязательно взаимодействовать друг с другом, если задачи не связаны между собой. **Если они не взаимодействуют, недетерминизм вполне приемлем.**
Например:
```js
var res = {};
function foo(results) {
res.foo = results;
}
function bar(results) {
res.bar = results;
}
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
```
`foo()` и `bar()` - это два конкурирующих "процесса", и нельзя однозначно определить в каком порядке они будут вызваны. Однако мы построили программу таким образом, что порядок их вызова не имеет значения, так как они работают независимо и, следовательно, не нуждаются во взаимодействии.
Это не ошибка «состояния гонки», так как код всегда будет работать правильно, независимо от порядка.
### Взаимодействие
Чаще всего, конкурирующие «процессы» будут по необходимости взаимодействовать косвенно через область видимости и/или DOM. Когда такое взаимодействие произойдет, вам необходимо координировать эти взаимодействия, чтобы предотвратить «состояние гонки», как описано ранее.
Вот простой пример двух конкурирующих «процессов», которые взаимодействуют из-за подразумеваемого порядка, который только *иногда нарушается*:
```js
var res = [];
function response(data) {
res.push( data );
}
ajax( "http://some.url.1", response );
ajax( "http://some.url.2", response );
```
Конкурирующие «процессы» — это два вызова `response()`, которые будут выполняться для обработки ответов Ajax. Они могут происходить в любом порядке.
Предположим, ожидаемое поведение состоит в том, что `res[0]` имеет результаты вызова `"http://some.url.1"`, а `res[1]` имеет результаты `"http:/ /some.url.2"` вызов. Иногда это будет иметь место, но иногда они будут перевернуты, в зависимости от того, какой вызов завершится первым. Существует довольно большая вероятность того, что этот недетерминизм является ошибкой «состояния гонки».
**Примечание.** Будьте крайне осторожны с предположениями, которые вы можете делать в подобных ситуациях. Например, разработчик нередко замечает, что «http://some.url.2» «всегда» отвечает намного медленнее, чем «http://some.url.1», возможно, из-за в силу того, какие задачи они выполняют (например, одна выполняет задачу базы данных, а другая просто извлекает статический файл), поэтому наблюдаемый порядок всегда выглядит так, как ожидалось. Даже если оба запроса отправляются на один и тот же сервер, и *он* намеренно отвечает в определенном порядке, нет *реальной* гарантии того, в каком порядке ответы будут возвращены в браузер.
Таким образом, для устранения такого состояния гонки вы можете координировать порядок взаимодействий:
```js
var res = [];
function response(data) {
if (data.url == "http://some.url.1") {
res[0] = data;
}
else if (data.url == "http://some.url.2") {
res[1] = data;
}
}
ajax( "http://some.url.1", response );
ajax( "http://some.url.2", response );
```
Независимо от того, какой ответ Ajax возвращается первым, мы проверяем `data.url` (предполагая, что он возвращен с сервера, конечно!), чтобы выяснить, какую позицию данные ответа должны занимать в массиве `res`. `res[0]` всегда будет содержать результаты `"http://some.url.1"`, а `res[1]` всегда будет содержать результаты `"http://some.url.2"`. Путем простой координации мы устранили недетерминизм «состояния гонки».
Те же рассуждения из этого сценария применимы, если несколько конкурирующих вызовов функций взаимодействуют друг с другом через общую модель DOM, например, один обновляет содержимое `<div>`, а другой обновляет стиль или атрибуты `<div>` (например, чтобы сделать элемент DOM видимым после того, как у него есть содержимое). Вы, вероятно, не захотите показывать элемент DOM до того, как у него будет содержимое, поэтому координация должна обеспечивать правильное взаимодействие с упорядочением.
Некоторые сценарии конкуренции *всегда ломаются* (а не просто *иногда*) без скоординированного взаимодействия.
Рассмотрим:
```js
var a, b;
function foo(x) {
a = x * 2;
baz();
}
function bar(y) {
b = y * 2;
baz();
}
function baz() {
console.log(a + b);
}
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
```
В этом примере, независимо от того, срабатывает ли сначала `foo()` или `bar()`, `baz()` всегда будет запускаться слишком рано (либо `a`, либо `b` все еще будут `undefined`), но второй вызов `baz()` будет работать, так как будут доступны и `a`, и `b`.
Существуют разные способы справиться с таким состоянием. Вот один простой способ:
```js
var a, b;
function foo(x) {
a = x * 2;
if (a && b) {
baz();
}
}
function bar(y) {
b = y * 2;
if (a && b) {
baz();
}
}
function baz() {
console.log( a + b );
}
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
```
Условие `if (a && b)` вокруг вызова `baz()` традиционно называется "воротами", потому что мы не уверены, какой порядок `a` и `b` прибудет, но мы ждем оба из них, чтобы попасть туда, прежде чем мы приступим к открытию ворот (вызов `baz()`).
Еще одно условие конкурентного взаимодействия, с которым вы можете столкнуться, иногда называют «гонкой», но правильнее называть его «защелкой». Характеризуется поведением «побеждает только первый». Здесь приемлем недетерминизм, поскольку вы явно говорите, что в «гонке» до финиша может быть только один победитель.
Рассмотрим этот сломанный код:
```js
var a;
function foo(x) {
a = x * 2;
baz();
}
function bar(x) {
a = x / 2;
baz();
}
function baz() {
console.log( a );
}
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
```
Какой бы из них (`foo()` или `bar()`) не срабатывал последним, он не только перезапишет назначенное значение `a` из другого, но также будет дублировать вызов `baz()` (вероятно, нежелательный).
Итак, мы можем согласовать взаимодействие с простой защелкой, чтобы пропускать только первую:
```js
var a;
function foo(x) {
if (a == undefined) {
a = x * 2;
baz();
}
}
function bar(x) {
if (a == undefined) {
a = x / 2;
baz();
}
}
function baz() {
console.log( a );
}
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
```
Условие `if (a == undefined)` допускает только первый из `foo()` или `bar()`, а второй (и любые последующие) вызовы будут просто проигнорированы. Нет ничего хорошего в том, чтобы занять второе место!
**Примечание.** Во всех этих сценариях мы использовали глобальные переменные для упрощения иллюстрации, но в наших рассуждениях здесь нет ничего, что требовало бы этого. Пока рассматриваемые функции могут получить доступ к переменным (через область видимости), они будут работать так, как задумано. Использование переменных с лексической областью видимости (см. название этой серии книг *Scope & Closures*) и фактически глобальных переменных, как в этих примерах, является очевидным недостатком этих форм координации в условиях конкуренции. В следующих нескольких главах мы увидим другие способы координации, которые в этом отношении намного чище.
### Кооперация
Другое выражение координации параллелизма называется «кооперативной конкуренцией» (cooperative concurrency). Здесь основное внимание уделяется не столько взаимодействию через совместное использование значений в областях видимости (хотя это, очевидно, все еще разрешено!). Цель состоит в том, чтобы взять длительный «процесс» и разбить его на шаги или пакеты, чтобы другие конкурирующие «процессы» имели возможность вставить свои операции в очереди цикла событий.
Например, рассмотрим обработчик ответа Ajax, которому необходимо просмотреть длинный список результатов для преобразования значений. Мы будем использовать `Array#map(..)`, чтобы сделать код короче:
```js
var res = [];
// `response(..)` получает массив результатов от Ajax-вызова
function response(data) {
// добавляем в существующий массив `res`
res = res.concat(
// создаем новый преобразованный массив со всеми удвоенными значениями `data`
data.map( function(val){
return val * 2;
} )
);
}
ajax( "http://some.url.1", response );
ajax( "http://some.url.2", response );
```
Если `"http://some.url.1"` сначала получит свои результаты, весь список будет сразу отображен в `res`. Если это несколько тысяч или меньше записей, это, как правило, не имеет большого значения. Но если это, скажем, 10 миллионов записей, это может занять некоторое время (несколько секунд на мощном ноутбуке, намного дольше на мобильном устройстве и т.д.).
Во время выполнения такого «процесса» на странице не может происходить ничего другого, включая другие вызовы `response(..)`, обновления пользовательского интерфейса, даже пользовательские события, такие как прокрутка, ввод текста, нажатие кнопки и тому подобное. Это довольно болезненно.
Таким образом, чтобы сделать более кооперативно конкурентную систему, более дружелюбную и не перегружающую очередь цикла событий, вы можете обрабатывать эти результаты асинхронными пакетами, после того как каждый из них «уступает» обратно в цикл событий, чтобы позволить произойти другим ожидающим событиям.
Вот очень простой подход:
```js
var res = [];
// `response(..)` получает массив результатов от Ajax-вызова
function response(data) {
// давайте просто сделаем 1000 за раз
var chunk = data.splice( 0, 1000 );
// добавляем в существующий массив `res`
res = res.concat(
// создаем новый преобразованный массив со всеми удвоенными значениями `chunk`
chunk.map( function(val){
return val * 2;
} )
);
// осталось что-нибудь обработать?
if (data.length > 0) {
// асинхронно планируем следующую партию
setTimeout( function(){
response( data );
}, 0 );
}
}
ajax( "http://some.url.1", response );
ajax( "http://some.url.2", response );
```
Мы обрабатываем набор данных блоками максимального размера по 1000 элементов. Поступая таким образом, мы обеспечиваем кратковременный «процесс», даже если это означает гораздо больше последующих «процессов», поскольку чередование с очередью цикла событий даст нам гораздо более отзывчивый (производительный) сайт/приложение.
Конечно, мы не координируем порядок взаимодействия любого из этих «процессов», поэтому порядок результатов в `res` не будет предсказуемым. Если бы требовался порядок, вам нужно было бы использовать методы взаимодействия, подобные тем, которые мы обсуждали ранее, или те, которые мы рассмотрим в следующих главах этой книги.
Мы используем `setTimeout(..0)` (хак) для асинхронного планирования, что в основном просто означает «вставить эту функцию в конец текущей очереди цикла событий».
**Примечание:** `setTimeout(..0)` технически не вставляет элемент непосредственно в очередь цикла событий. Таймер вставит событие при следующей возможности. Например, два последовательных вызова `setTimeout(..0)` не будут строго гарантированы для обработки в порядке вызова, поэтому *возможно* увидеть различные условия, такие как дрейф таймера, когда порядок таких событий не предсказуем. В Node.js аналогичный подход — `process.nextTick(..)`. Несмотря на то, насколько удобным (и, как правило, более производительным) это было бы, не существует единого прямого способа (по крайней мере, пока) во всех средах для обеспечения асинхронного упорядочения событий. Мы рассмотрим эту тему более подробно в следующем разделе.
## Задачи
Начиная с ES6, появилась новая концепция над очередью цикла событий, которая называется "Очередь задач". Скорее всего, вам придется столкнуться с асинхронным поведением промисов (см. главу 3).
К сожалению, на данный момент это механизм без открытого API, поэтому демонстрация его немного сложнее. Так что нам нужно просто описать его концептуально, чтобы, когда мы будем обсуждать асинхронное поведение с промисами в главе 3, вы поняли, как эти действия планируются и обрабатываются.
Итак, лучший способ думать об этом, который я нашел, состоит в том, что «Очередь задач» — это очередь, свисающая с конца каждого такта в очереди цикла событий. Некоторые подразумеваемые асинхронные действия, которые могут произойти во время такта цикла событий, не приведут к добавлению нового события в очередь цикла событий, а вместо этого добавят элемент (также известный как задача) в конец очереди задач текущего такта.
Это все равно, что сказать: «О, вот еще одна вещь, которую мне нужно сделать *позже*, но убедитесь, что это произойдет прямо сейчас, прежде чем что-либо еще может произойти».
Или, если использовать метафору: очередь цикла событий подобна аттракциону в парке развлечений, где, как только вы закончите кататься, вам нужно вернуться в конец очереди, чтобы прокатиться снова. Но очередь задач похожа на то, как если бы вы закончили поездку, но затем встали в очередь и сразу же вернулись.
Задача также может привести к добавлению дополнительных задач в конец одной и той же очереди. Таким образом, теоретически возможно, что «цикл» задач (задача, которая продолжает добавлять другую задачу и т.д.) может вращаться бесконечно, лишая программу возможности перейти к следующему такту цикла событий. Концептуально это было бы почти так же, как простое выражение длительного или бесконечного цикла (например, `while (true) ..`) в вашем коде.
Задачи похожи на дух хака `setTimeout(..0)`, но реализованы таким образом, чтобы иметь гораздо более четко определенный и гарантированный порядок: **позже, но как можно скорее**.
Давайте представим API для планирования задач (напрямую, без хаков) и назовем его `schedule(..)`.
```js
console.log( "A" );
setTimeout( function(){
console.log( "B" );
}, 0 );
// теоретический "API задачи"
schedule( function(){
console.log( "C" );
schedule( function(){
console.log( "D" );
} );
} );
```
Вы можете ожидать, что это напечатает `A B C D`, но вместо этого будет напечатано `A C D B`, потому что задачи происходят в конце текущего такта цикла событий, и таймер срабатывает, чтобы запланировать *следующий* такт цикла событий (если доступно!).
В главе 3 мы увидим, что асинхронное поведение промисов основано на задачах, поэтому важно четко понимать, как это связано с поведением цикла обработки событий.
## Порядок операторов
Порядок, в котором мы выражаем операторы в нашем коде, не обязательно совпадает с порядком, в котором JS-движок будет их выполнять. Это может показаться довольно странным утверждением, поэтому мы кратко рассмотрим его.
Но прежде чем мы это сделаем, мы должны кое-что предельно ясно уяснить: правила/грамматика языка (см. название этой серии книг *Типы и грамматика*) диктуют очень предсказуемое и надежное поведение для упорядочения операторов с точки зрения программы. Итак, то, что мы собираемся обсудить, — это **не то, что вы когда-либо сможете наблюдать** в своей JS-программе.
**Предупреждение:** Если вы когда-либо сможете *наблюдать* переупорядочивание операторов компилятора, как мы собираемся проиллюстрировать, это будет явным нарушением спецификации, и это, несомненно, будет связано с ошибкой в движке JS. в вопросе - тот, который должен быть немедленно сообщен и исправлен! Но гораздо чаще вы *подозреваете* что-то сумасшедшее происходит в движке JS, когда на самом деле это просто ошибка (вероятно, "состояние гонки"!) в вашем собственном коде - так что сначала смотрите туда, и снова и снова . Отладчик JS, использующий точки останова и последовательно выполняющий код, станет вашим самым мощным инструментом для обнаружения таких ошибок в *вашем коде*.
Рассмотреть возможность:
```js
var a, b;
a = 10;
b = 30;
a = a + 1;
b = b + 1;
console.log( a + b ); // 42
```
Этот код не имеет выраженной асинхронности (за исключением редкого «консольного» асинхронного ввода-вывода, который обсуждался ранее!), поэтому наиболее вероятным предположением будет то, что он будет обрабатывать строку за строкой сверху вниз.
Но *возможно*, что движок JS после компиляции этого кода (да, JS скомпилирован — см. название *Scope & Closures* этой серии книг!) может найти возможности для более быстрого запуска вашего кода, перестраивая (безопасно) порядок этих выражений. По сути, пока вы не можете наблюдать за изменением порядка, все в порядке.
Например, движок может решить, что на самом деле быстрее выполнить такой код:
```js
var a, b;
a = 10;
a++;
b = 30;
b++;
console.log( a + b ); // 42
```
Или это:
```js
var a, b;
a = 11;
b = 31;
console.log( a + b ); // 42
```
Или даже:
```js
// так как `a` и `b` больше не используются, мы можем
// встроить их и даже не нуждаться в них!
console.log( 42 ); // 42
```
Во всех этих случаях движок JS выполняет безопасную оптимизацию во время компиляции, так как конечный *наблюдаемый* результат будет одним и тем же.
Но вот сценарий, в котором эти конкретные оптимизации были бы небезопасными и, следовательно, не могли бы быть разрешены (конечно, нельзя сказать, что они вообще не оптимизированы):
```js
var a, b;
a = 10;
b = 30;
// нам нужны `a` и `b` в их предварительно увеличенном состоянии!
console.log( a * b ); // 300
a = a + 1;
b = b + 1;
console.log( a + b ); // 42
```
Другие примеры, когда переупорядочивание компилятора может создать наблюдаемые побочные эффекты (и, следовательно, должно быть запрещены), включают в себя такие вещи, как любой вызов функции с побочными эффектами (даже и особенно функции-получатели) или прокси-объекты ES6 (см. заголовок *ES6 & Beyond* статьи эту серию книг).
Рассмотрим возможность:
```js
function foo() {
console.log( b );
return 1;
}
var a, b, c;
// Синтаксис литерала геттера ES5.1
c = {
get bar() {
console.log( a );
return 1;
}
};
a = 10;
b = 30;
a += foo(); // 11
b += c.bar; // 31
console.log( a + b ); // 42
```
Если бы не операторы `console.log(..)` в этом фрагменте (просто используемые как удобная форма наблюдаемого побочного эффекта для иллюстрации), движок JS, вероятно, мог бы свободно, если бы захотел (кто знает, захочет ли!?), изменить порядок кода:
```js
// ...
a = 10 + foo();
b = 30 + c.bar;
// ...
```
В то время как семантика JS, к счастью, защищает нас от *наблюдаемых* кошмаров, которым может угрожать переупорядочивание операторов компилятора, по-прежнему важно понимать, насколько незначительна связь между способом создания исходного кода (сверху вниз) и как он работает после компиляции.
Переупорядочивание операторов компилятора — это почти микрометафора конкурентности и взаимодействия. В целом такая осведомленность может помочь вам лучше понять проблемы потока асинхронного кода JS.
## Обзор
Программа JavaScript (практически) всегда разбивается на две или более частей, где первая часть выполняется *сейчас*, а следующая часть выполняется *позже*, в ответ на событие. Несмотря на то, что программа выполняется по частям, все они имеют одинаковый доступ к области действия и состоянию программы, поэтому каждое изменение состояния выполняется поверх предыдущего состояния.
Всякий раз, когда есть события для запуска, *цикл событий* выполняется до тех пор, пока очередь не станет пустой. Каждая итерация цикла событий — это "такт". Взаимодействие с пользователем, ввод-вывод и таймеры помещают события в очередь событий.
В любой момент времени из очереди может быть обработано только одно событие. Во время выполнения события оно может прямо или косвенно вызывать одно или несколько последующих событий.
Конкурентность — это когда две или более цепочек событий чередуются во времени, так что с точки зрения высокого уровня кажется, что они выполняются *одновременно* (даже если в любой момент обрабатывается только одно событие).
Часто бывает необходимо выполнить какую-то форму координации взаимодействия между этими конкурирующими «процессами» (в отличие от процессов операционной системы), например, чтобы обеспечить упорядоченность или предотвратить «состояние гонки». Эти «процессы» также могут *взаимодействовать*, разбивая себя на более мелкие фрагменты и допуская чередование других «процессов».
================================================
FILE: async & performance/ch2.md
================================================
# Вы не знаете JS: Асинхронность и Выполнение
# Глава 2: Колбеки
В главе 1 мы рассмотрели терминологию и концепции асинхронного программирования в JavaScript. Наше внимание сосредоточено на понимании однопоточной (по одному за раз) очереди цикла событий, которая управляет всеми «событиями» (вызовами асинхронных функций). Мы также исследовали различные способы, которыми шаблоны конкурентности объясняют отношения (если они есть!) между *одновременно* запущенными цепочками событий или «процессами» (задачами, вызовами функций и т.д.).
Во всех наших примерах в главе 1 функция использовалась как отдельная, неделимая единица операций, при этом внутри функции операторы выполняются в предсказуемом порядке (выше уровня компилятора!), но на уровне упорядочения функций события (также известные как асинхронные вызовы функций) могут происходить в различных порядках.
Во всех этих случаях функция действует как «обратный вызов», потому что она служит целью цикла обработки событий для «обратного вызова» программы всякий раз, когда обрабатывается этот элемент в очереди.
Как вы, несомненно, заметили, обратные вызовы являются наиболее распространенным способом выражения и управления асинхронностью в JS-программах. Действительно, обратный вызов является наиболее фундаментальным асинхронным шаблоном в языке.
Бесчисленные JS-программы, даже очень изощренные и сложные, были написаны только на основе коллбеков (конечно, с использованием шаблонов параллелизма, которые мы рассмотрели в главе 1). Функция обратного вызова — это асинхронная рабочая лошадка для JavaScript, и она достойно выполняет свою работу.
За исключением... обратные вызовы не лишены недостатков. Многие разработчики воодушевлены *обещанием* (каламбур!) улучшенных асинхронных шаблонов. Но невозможно эффективно использовать любую абстракцию, если вы не понимаете, что она абстрагирует и почему.
В этой главе мы подробно рассмотрим пару из них, чтобы понять, почему необходимы и желательны более сложные асинхронные шаблоны (рассматриваемые в последующих главах этой книги).
## Продолжения
Давайте вернемся к примеру с асинхронным обратным вызовом, с которого мы начали в главе 1, но позвольте мне немного изменить его, чтобы проиллюстрировать один момент:
```js
// A
ajax( "..", function(..){
// C
} );
// B
```
`// A` и `// B` представляют первую половину программы (она же *сейчас*), а `//C` отмечает вторую половину программы (она же *позже*). Первая половина выполняется сразу, а потом идет "пауза" неопределенной длины. В какой-то момент в будущем, если вызов Ajax завершится, программа продолжит с того места, где остановилась, и *продолжит* со второй половиной.
Другими словами, функция обратного вызова оборачивает или инкапсулирует *продолжение* программы.
Сделаем код еще проще:
```js
// A
setTimeout( function(){
// C
}, 1000 );
// B
```
Остановитесь на мгновение и спросите себя, как бы вы описали (кому-то другому, менее осведомленному о том, как работает JS) поведение этой программы. Давай, попробуй вслух. Это хорошее упражнение, которое поможет моим следующим пунктам стать более осмысленными.
Большинство читателей сейчас, вероятно, подумали или сказали что-то вроде: «Выполните A, затем установите тайм-аут на 1000 миллисекунд, затем, как только это сработает, выполните C». Насколько точно было ваше толкование?
Возможно, вы поймали себя на том, что сами отредактировали: «Выполните А, установите тайм-аут на 1000 миллисекунд, затем выполните B, затем, когда истечет тайм-аут, сделайте С». Это более точно, чем первая версия. Вы можете заметить разницу?
Несмотря на то, что вторая версия является более точной, обе версии не могут объяснить этот код таким образом, чтобы наш мозг соответствовал коду, а код — движку JS. Это отключение одновременно тонкое и монументальное, и оно лежит в основе понимания недостатков обратных вызовов как асинхронного выражения и управления.
Как только мы вводим одно продолжение (или несколько десятков, как многие программы!) в виде функции обратного вызова, мы позволяем образоваться расхождению между тем, как работает наш мозг, и тем, как будет работать код. Каждый раз, когда эти двое расходятся (и это далеко не единственное место, где это происходит, как я уверен, вы знаете!), мы сталкиваемся с неизбежным фактом, что наш код становится труднее понимать, анализировать, отлаживать и поддерживать.
## Последовательный мозг
Я почти уверен, что большинство из вас, читатели, слышали, как кто-то сказал (даже сами заявили): «Я многозадачный». Эффекты от попытки действовать в качестве многозадачного человека варьируются от забавных (например, глупая детская игра «погладь голову-потирай-живот») до обыденных (жуй жвачку на ходу) и откровенно опасных (текстовые сообщения за рулем).
Но многозадачны ли мы? Можем ли мы действительно совершать два сознательных, преднамеренных действия одновременно и думать/рассуждать о них обоих в один и тот же момент? Есть ли у нашего самого высокого уровня функциональности мозга параллельная многопоточность?
Ответ может вас удивить: **вероятно, нет.**
На самом деле наш мозг просто не так устроен. Мы гораздо больше занимаемся одиночными делами, чем многие из нас (особенно личности типа А!) хотели бы признать. На самом деле мы можем думать только об одном в любой данный момент.
Я не говорю обо всех наших непроизвольных, подсознательных, автоматических функциях мозга, таких как сердцебиение, дыхание и моргание век. Все это жизненно важные задачи для нашей устойчивой жизни, но мы намеренно не выделяем для них никаких умственных способностей. К счастью, пока мы одержимы проверкой ленты социальных сетей в 15-й раз за три минуты, наш мозг продолжает выполнять все эти важные задачи в фоновом режиме (потоки!)
Вместо этого мы говорим о любой задаче, которая находится в центре нашего внимания в данный момент. Для меня это написание текста в этой книге прямо сейчас. Выполняю ли я какую-либо другую функцию мозга более высокого уровня точно в этот же момент? Нет, не совсем. Я отвлекаюсь быстро и легко — несколько десятков раз в этих последних парах абзацев!
Когда мы *имитируем* многозадачность, например, пытаемся что-то напечатать во время разговора с другом или членом семьи по телефону, на самом деле мы, скорее всего, действуем как быстрые переключатели контекста. Другими словами, мы переключаемся между двумя или более задачами в быстрой последовательности, *одновременно* продвигаясь по каждой задаче маленькими, быстрыми порциями. Мы делаем это так быстро, что внешнему миру кажется, будто мы делаем эти вещи *параллельно*.
Звучит ли это для вас подозрительно как конкурентность с асинхронными событиями (подобная той, что происходит в JS)?! Если нет, вернитесь и прочитайте главу 1 еще раз!
На самом деле, один из способов упростить (т. е. злоупотребить) невероятно сложный мир неврологии и превратить его в то, что я могу отдалённо надеяться обсудить здесь, заключается в том, что наш мозг работает примерно так же, как очередь цикла событий.
Если вы думаете о каждой отдельной букве (или слове), которую я печатаю, как об одном асинхронном событии, только в этом предложении есть несколько десятков возможностей для моего мозга быть прерванным каким-то другим событием, например, от моих органов чувств или даже просто от моего сознания. случайные мысли.
Меня не прерывают и не вовлекают в другой «процесс» при каждой возможности (к счастью, иначе эта книга никогда не была бы написана!). Но достаточно часто случается так, что я чувствую, что мой собственный мозг почти постоянно переключается на разные контексты (также известные как «процессы»). И это очень похоже на то, как, вероятно, чувствовал бы себя движок JS.
### Делать VS Планировать
Итак, наш мозг можно представить как работающий в однопоточной очереди цикла событий подобно движку JS. Это звучит как хорошая аналогия.
Но нам нужно быть более тонкими анализируя это. Существует большая, заметная разница между тем, как мы планируем различные задачи, и тем, как наш мозг на самом деле выполняет эти задачи.
Опять же, вернемся к написанию этого текста как моей метафоры. Мой грубый ментальный план состоит в том, чтобы продолжать писать и писать, последовательно проходя через набор пунктов, которые я упорядочил в своих мыслях. Я не планирую никаких перерывов или нелинейной активности в этом письме. Но тем не менее мой мозг все время переключается.
Несмотря на то, что на операционном уровне наш мозг работает асинхронно, кажется, что мы планируем задачи последовательно и синхронно. «Мне нужно сходить в магазин, потом купить молока, а потом сдать вещи из химчистки».
Вы заметите, что это мышление более высокого уровня (планирование) не кажется очень асинхронным в своей формулировке. На самом деле, мы редко намеренно думаем исключительно с точки зрения событий. Вместо этого мы планируем все тщательно, последовательно (А затем Б затем С), и мы допускаем до некоторой степени своего рода временную блокировку, которая заставляет Б ждать А, а С ждать Б.
Когда разработчик пишет код, он планирует ряд действий, которые должны произойти. Если он хороший разработчик, то он **тщательно планирует** это. «Мне нужно установить `z` в значение `x`, а затем `x` в значение `y`» и так далее.
Когда мы пишем синхронный код, оператор за оператором, он работает во многом подобно нашему списку дел:
```js
// поменять местами `x` и `y` (через временную переменную `z`)
z = x;
x = y;
y = z;
```
Эти три оператора присваивания являются синхронными, поэтому `x = y` ожидает завершения `z = x`, а `y = z`, в свою очередь, ожидает завершения `x = y`. Другими словами, эти три утверждения связаны во времени с выполнением в определенном порядке, одно за другим. К счастью, нам не нужно беспокоиться о каких-либо подробностях, связанных с асинхронными событиями. Если бы мы это сделали, код быстро стал бы намного сложнее!
Итак, если синхронное планирование мозга хорошо соотносится с операторами синхронного кода, насколько хорошо наш мозг справляется с планированием асинхронного кода?
Оказывается, то, как мы выражаем асинхронность (с обратными вызовами) в нашем коде, совсем не соответствует этому синхронному поведению планирования мозга.
Можете ли вы на самом деле представить, что у вас есть ход мыслей, который планирует ваши дела таким образом?
> "Мне нужно в магазин, но по дороге я уверен, что мне позвонят, так что "Привет, мама", и пока она начнет говорить, я поищу адрес магазина по GPS, но это займет секунду, поэтому я убавлю радио, чтобы лучше слышать маму, а потом пойму, что забыл надеть куртку, а на улице холодно, но неважно, продолжаю ехать и разговариваю с мамой, а затем звон ремня безопасности напоминает мне о том, что нужно пристегнуться, так что «Да, мама, я пристегнут ремнем безопасности, я всегда пристегиваюсь!». Ах, наконец-то GPS получил направление, теперь...»
Как бы нелепо это ни звучало как формулировка того, как мы планируем свой день и думаем о том, что делать и в каком порядке, тем не менее именно так работает наш мозг на функциональном уровне. Помните, что это не многозадачность, это просто быстрое переключение контекста.
Причина, по которой нам, как разработчикам, трудно писать асинхронный код с событиями, особенно когда все, что у нас есть, это обратный вызов, заключается в том, что поток мыслей/планирование потока сознания неестественен для большинства из нас.
Мы думаем пошагово, но инструменты (обратные вызовы), доступные нам в коде, не выражаются пошагово, как только мы переходим от синхронного к асинхронному.
И **вот** почему так сложно точно написать и обосновать асинхронный код JS с обратными вызовами: потому что это не то, как работает наш мозг.
**Примечание:** Единственное, что может быть хуже, чем не знать, почему некоторые коды не работают, — это не знать, почему они вообще работают! Это классический менталитет «карточного домика»: «он работает, но не знаю почему, поэтому никто его не трогает!» Возможно, вы слышали «Ад — это другие люди» (Сартр), а программистский мем — «Ад — это код других людей». Я искренне верю: «Ад - это не понимать собственный код». И обратные вызовы являются одним из основных виновников.
### Вложенные/связанные обратные вызовы
Рассмотрим:
```js
listen( "click", function handler(evt){
setTimeout( function request(){
ajax( "http://some.url.1", function response(text){
if (text == "hello") {
handler();
}
else if (text == "world") {
request();
}
} );
}, 500) ;
} );
```
Есть хорошие шансы, что такой код узнаваем для вас. У нас есть цепочка из трех вложенных друг в друга функций, каждая из которых представляет собой шаг в асинхронной последовательности (задача, «процесс»).
Этот тип кода часто называют «ад обратных вызовов», а иногда также называют «пирамидой гибели» (из-за его треугольной формы, обращенной вбок из-за вложенного углубления).
Но «ад обратных вызовов» на самом деле не имеет почти ничего общего с вложенностью/отступом. Это гораздо более глубокая проблема. Мы увидим, как и почему, в оставшейся части этой главы.
Во-первых, мы ждем события «click», затем мы ждем срабатывания таймера, затем мы ждем возврата ответа Ajax, после чего он может повторить все это снова.
На первый взгляд может показаться, что этот код естественным образом связывает свою асинхронность с последовательным мозговым планированием.
Сначала (*сейчас*):
```js
listen( "..", function handler(..){
// ..
} );
```
*позже*:
```js
setTimeout( function request(..){
// ..
}, 500) ;
```
*еще позже*:
```js
ajax( "..", function response(..){
// ..
} );
```
Наконец (наиболее *позже*):
```js
if ( .. ) {
// ..
}
else ..
```
Но есть несколько проблем с линейными рассуждениями об этом коде.
Во-первых, случайность примера состоит в том, что наши шаги находятся на следующих строках (1, 2, 3 и 4...). В настоящих асинхронных JS-программах часто присутствует гораздо больше шума, загромождающего вещи, которым мы должны ловко маневрировать в своем мозгу, когда переходим от одной функции к другой. Понимание асинхронного потока в таком перегруженном обратными вызовами коде не является невозможным, но это определенно не естественно и не легко, даже при большой практике.
Но также есть и более глубокая ошибка, которая не очевидна только в этом примере кода. Позвольте мне придумать другой сценарий (псевдокодовый), чтобы проиллюстрировать его:
```js
doA( function(){
doB();
doC( function(){
doD();
} )
doE();
} );
doF();
```
Хотя опытные из вас правильно определят здесь истинный порядок операций, я держу пари, что на первый взгляд он более чем немного сбивает с толку, и для его достижения требуется несколько согласованных умственных циклов. Операции будут происходить в таком порядке:
* `doA()`
* `doF()`
* `doB()`
* `doC()`
* `doE()`
* `doD()`
Вы поняли это правильно, когда впервые взглянули на код?
Хорошо, некоторые из вас думают, что я несправедливо назвал свои функции, чтобы намеренно ввести вас в заблуждение. Клянусь, я просто называл в порядке появления сверху вниз. Но позвольте мне попробовать еще раз:
```js
doA( function(){
doC();
doD( function(){
doF();
} )
doE();
} );
doB();
```
Теперь я назвал их в алфавитном порядке в порядке фактического выполнения. Но я все еще держу пари, даже имея опыт в этом сценарии, отслеживание в порядке «A -> B -> C -> D -> E -> F» не является естественным для многих из вас, читатели. Конечно, ваши глаза очень много прыгают вверх и вниз по фрагменту кода, верно?
Но даже если все это кажется вам естественным, есть еще одна опасность, которая может нанести ущерб. Можете ли вы определить, что это такое?
Что, если `doA(..)` или `doD(..)` на самом деле не являются асинхронными, как мы, очевидно, предполагали? Ой, теперь порядок другой. Если они оба синхронизированы (и, возможно, только иногда, в зависимости от условий программы в то время), порядок теперь будет «A -> C -> D -> F -> E -> B».
Тот звук, который вы только что услышали на заднем плане, — это вздохи тысяч JS-разработчиков, которые только что столкнулись лицом к лицу.
Является ли вложение проблемой? Из-за этого так сложно отследить асинхронный поток? Это часть этого, конечно.
Но позвольте мне переписать предыдущий пример вложенного события/тайм-аута/Ajax без использования вложенности:
```js
listen( "click", handler );
function handler() {
setTimeout( request, 500 );
}
function request(){
ajax( "http://some.url.1", response );
}
function response(text){
if (text == "hello") {
handler();
}
else if (text == "world") {
request();
}
}
```
Эта формулировка кода едва ли так узнаваема, как проблемы вложенности/отступов в его предыдущей форме, и все же она ничуть не менее подвержена «аду обратных вызовов». Почему?
По мере того, как мы переходим к линейному (последовательному) анализу этого кода, мы должны переходить от одной функции к следующей, к следующей и перемещаться по кодовой базе, чтобы «увидеть» поток последовательности. И помните, это упрощенный код в наилучшем случае. Все мы знаем, что базы кода реальных асинхронных JS-программ зачастую фантастически более запутаны, что на несколько порядков усложняет такие рассуждения.
Еще одна вещь, на которую следует обратить внимание: чтобы связать шаги 2, 3 и 4 вместе, чтобы они выполнялись последовательно, единственный доступный способ, которые обратные вызовы дают нам, — это жестко закодировать шаг 2 в шаг 1, шаг 3 в шаг 2, шаг 4 в шаг 3, и так далее. Жесткое кодирование не обязательно плохо, если это действительно фиксированное условие, что шаг 2 всегда должен вести к шагу 3.
Но жесткое кодирование определенно делает код немного более хрупким, поскольку оно не учитывает никаких ошибок, которые могут вызвать отклонение в последовательности шагов. Например, если шаг 2 терпит неудачу, шаг 3 никогда не достигается, и шаг 2 не повторяется, или выполняется переход к альтернативному потоку обработки ошибок и т.д.
Все эти проблемы вы *можете* вручную жестко прописать в каждом шаге, но этот код часто очень повторяющийся и не может быть повторно использован на других шагах или в других асинхронных потоках в вашей программе.
Несмотря на то, что наш мозг может планировать ряд задач в последовательном порядке (это, затем это, затем это), событийный характер работы нашего мозга делает восстановление/повторение/разветвление управления потоком практически без усилий. Если вы бегаете по делам и понимаете, что оставили дома список покупок, день не заканчивается, потому что вы не спланировали это заранее. Ваш мозг легко обходит эту заминку: вы идете домой, берете список и сразу же направляетесь обратно в магазин.
Но хрупкая природа жестко закодированных вручную обратных вызовов (даже с жестко запрограммированной обработкой ошибок) часто гораздо менее изящна. Как только вы в конечном итоге укажете (также известное как предварительное планирование) все различные возможности/пути, код станет настолько запутанным, что его трудно будет поддерживать или обновлять.
**Вот это** и есть "ад обратных вызовов"! Вложение/отступы - это, по сути, второстепенное шоу, отвлекающий маневр.
И как будто всего этого недостаточно, мы даже не коснулись того, что происходит, когда две или более цепочек этих продолжений обратного вызова происходят *одновременно*, или когда третий шаг разветвляется на «параллельные» обратные вызовы с воротами или защелками, или... ОМГ, у меня мозг болит, а у тебя?!
Улавливаете ли вы здесь, что наше последовательное, блокирующее поведение планирования мозга просто не очень хорошо отображается в асинхронном коде, ориентированном на обратных вызовах? Это первый существенный недостаток обратных вызовов, о котором нужно сказать: они выражают асинхронность в коде таким образом, что нашему мозгу приходится бороться только за то, чтобы синхронизироваться с ним (каламбур!).
## Проблемы с доверием
Несоответствие между последовательным мозговым планированием и асинхронным кодом JS, управляемым обратными вызовами, — это только часть проблемы с обратными вызовами. Есть кое-что гораздо более глубокое, о чем следует беспокоиться.
Давайте еще раз вернемся к понятию callback-функции как продолжения (она же вторая половина) нашей программы:
```js
// A
ajax( "..", function(..){
// C
} );
// B
```
`// A` и `// B` происходят *сейчас* под непосредственным контролем основной JS-программы. Но `// C` откладывается, чтобы произойти *позже*, и находится под контролем другой стороны – в данном случае, функции `ajax(..)`. В общем смысле такая передача управления обычно не вызывает много проблем для программ.
Но пусть вас не вводит в заблуждение его редкость, что этот переключатель управления не имеет большого значения. На самом деле, это одна из самых серьезных (и в то же время наиболее тонких) проблем проектирования, основанного на обратных вызовах. Он вращается вокруг идеи, что иногда `ajax(..)` (т.е. "сторона", которой вы передаете продолжение обратного вызова) не является функцией, которую вы написали или которой вы непосредственно управляете. Часто это утилита, предоставляемая третьей стороной.
Мы называем это «инверсией управления (IoC)», когда вы берете часть своей программы и передаете контроль над ее выполнением другому третьему лицу. Между вашим кодом и сторонней утилитой существует негласный «контракт» — набор вещей, которые вы ожидаете поддерживать.
### Сказка о пяти обратных вызовах
Может быть не совсем очевидно, почему это так важно. Позвольте мне построить преувеличенный сценарий, чтобы проиллюстрировать опасности доверия в игре.
Представьте, что вы разработчик, которому поручено создать систему оплаты электронной торговли для сайта, продающего дорогие телевизоры. У вас уже есть все различные страницы системы оформления заказов, созданные просто отлично. На последней странице, когда пользователь нажимает «подтвердить», чтобы купить телевизор, вам нужно вызвать стороннюю функцию (предоставленную, скажем, какой-то аналитической компанией), чтобы можно было отследить продажу.
Вы заметили, что они предоставили то, что выглядит как утилита асинхронного отслеживания, вероятно, ради лучших практик производительности, что означает, что вам нужно передать функцию обратного вызова. В этом продолжении, которое вы передаете, у вас будет окончательный код, который снимает средства с кредитной карты клиента и отображает страницу благодарности.
Этот код может выглядеть так:
```js
analytics.trackPurchase( purchaseData, function(){
chargeCreditCard();
displayThankyouPage();
} );
```
Достаточно легко, верно? Вы пишете код, тестируете его, все работает, и вы запускаете его в производство. Все счастливы!
Прошло полгода и никаких проблем. Вы почти забыли, что даже написали этот код. Однажды утром вы сидите в кофейне перед работой, небрежно наслаждаясь латте, когда вам звонит в панике начальник и настаивает, чтобы вы бросили кофе и сразу же бросились на работу.
Когда вы приедете, вы узнаете, что с кредитной карты высокопоставленного клиента пять раз списали средства за один и тот же телевизор, и он, по понятным причинам, расстроен. Служба поддержки клиентов уже принесла извинения и обработала возврат средств. Но ваш босс требует знать, как это могло произойти. «Разве у нас нет тестов на подобные вещи!?»
Вы даже не помните код, который вы написали. Но вы копаетесь и начинаете пытаться выяснить, что могло пойти не так.
Покопавшись в некоторых логах, вы приходите к выводу, что единственное объяснение в том, что утилита аналитики каким-то образом по какой-то причине вызвала ваш обратный вызов пять раз вместо одного. Ничто в их документации не упоминает ничего об этом.
Разочарованный, вы связываетесь со службой поддержки, которая, конечно, так же удивлена, как и вы. Они соглашаются сообщить об этом своим разработчикам и обещают вернуться к вам. На следующий день вы получаете длинное электронное письмо с объяснением того, что они нашли, и сразу же пересылаете его своему боссу.
Судя по всему, разработчики аналитической компании работали над некоторым экспериментальным кодом, который при определенных условиях будет повторять предоставленный обратный вызов один раз в секунду в течение пяти секунд, прежде чем произойдет сбой с тайм-аутом. Они никогда не собирались продвигать это в производство, но каким-то образом они это сделали, и они полностью смущены и извиняются. Они подробно рассказывают о том, как они определили сбой и что они сделают, чтобы это никогда не повторилось.
Что дальше?
Вы обсуждаете это со своим боссом, но он не чувствует себя особенно довольным положением вещей. Он настаивает, и вы неохотно соглашаетесь, что вы не можете больше доверять *им* (это то, что вас задело), и что вам нужно снова придумать, как защитить код проверки от такой уязвимости.
После некоторой доработки вы реализуете простой специальный код, подобный приведенному ниже, который, похоже, устраивает команду:
```js
var tracked = false;
analytics.trackPurchase( purchaseData, function(){
if (!tracked) {
tracked = true;
chargeCreditCard();
displayThankyouPage();
}
} );
```
**Примечание:** Это должно показаться вам знакомым из главы 1, потому что мы, по сути, создаем защелку для обработки, если произойдет несколько одновременных вызовов нашего обратного вызова.
Но затем один из ваших QA-инженеров спрашивает: «Что произойдет, если они никогда не вызовут обратный вызов?» Упс. Никто из вас не думал об этом.
Вы начинаете искать кроличью нору и думаете обо всех возможных вещах, которые могут пойти не так, если они перезвонят вам. Вот примерный список возможных сбоев в работе аналитической утилиты:
* Вызывать обратный вызов слишком рано (до того, как он будет отслежен)
* Вызов обратного вызова слишком поздно (или никогда)
* Вызовите обратный вызов слишком мало или слишком много раз (например, проблема, с которой вы столкнулись!)
* Не удалось передать любую необходимую среду/параметры вашему обратному вызову
* Проглотить любые ошибки/исключения, которые могут произойти
* ...
Это должно показаться тревожным списком, потому что так оно и есть. Вы, вероятно, постепенно начинаете понимать, что вам придется изобретать очень много специальной логики **в каждом обратном вызове**, которая передается утилите, которой вы не уверены, что можете доверять.
Теперь вы немного более полно осознаете, насколько адским является «ад обратных вызовов».
### Не только чужой код
Некоторые из вас могут сейчас скептически отнестись к тому, настолько ли это важно, как я это преподношу. Возможно, вы мало взаимодействуете с действительно сторонними утилитами. Возможно, вы используете версионные API или самостоятельно размещаете такие библиотеки, чтобы их поведение нельзя было изменить без вас.
Итак, подумайте над этим: можете ли вы вообще *действительно* доверять утилитам, которыми вы теоретически управляете (в своей собственной кодовой базе)?
Подумайте об этом так: большинство из нас согласны с тем, что, по крайней мере, в некоторой степени мы должны создавать свои собственные внутренние функции с некоторыми защитными проверками входных параметров, чтобы уменьшить/предотвратить непредвиденные проблемы.
Чрезмерное доверие входным данным:
```js
function addNumbers(x,y) {
// + перегружен приведением,
// чтобы также быть конкатенацией строк,
// поэтому эта операция не является строго безопасной
// в зависимости от того, что передано.
return x + y;
}
addNumbers( 21, 21 ); // 42
addNumbers( 21, "21" ); // "2121"
```
Защита от ненадежного ввода:
```js
function addNumbers(x,y) {
// обеспечиваем числовой ввод
if (typeof x != "number" || typeof y != "number") {
throw Error( "Bad parameters" );
}
// если мы доберемся сюда, + безопасно выполнит числовое сложение
return x + y;
}
addNumbers( 21, 21 ); // 42
addNumbers( 21, "21" ); // Error: "Bad parameters"
```
Или, возможно, все еще безопасно, но дружелюбнее:
```js
function addNumbers(x,y) {
// обеспечиваем числовой ввод
x = Number( x );
y = Number( y );
// + будет безопасно делать числовое сложение
return x + y;
}
addNumbers( 21, 21 ); // 42
addNumbers( 21, "21" ); // 42
```
Как бы вы это ни делали, такого рода проверки/нормализации довольно распространены на входных данных функций, даже с кодом, которому мы теоретически полностью доверяем. Грубо говоря, это похоже на программный эквивалент геополитического принципа «Доверяй, но проверяй».
Итак, не разумно ли, что мы должны делать то же самое с композицией обратных вызовов асинхронных функций, не только с действительно внешним кодом, но даже с кодом, который, как мы знаем, обычно «находится под нашим собственным контролем»? **Конечно, должны.**
Но обратные вызовы на самом деле не предлагают ничего, чтобы помочь нам. Мы должны создавать весь этот механизм сами, и часто это приводит к большому количеству шаблонов/накладных расходов, которые мы повторяем для каждого отдельного асинхронного обратного вызова.
Самая неприятная проблема с обратными вызовами — это *инверсия управления*, ведущая к полному разрыву всех этих линий доверия.
Если у вас есть код, который использует обратные вызовы, особенно, но не исключительно, со сторонними утилитами, и вы еще не применяете какую-то логику смягчения для всех этих *инверсии управления* проблем с доверием, ваш код *имеет* ошибки в нем прямо сейчас, хотя они, возможно, еще не укусили вас. Скрытые ошибки остаются ошибками.
Действительно ад.
## Попытка сохранить обратные вызовы
Существует несколько вариантов дизайна обратного вызова, которые пытались решить некоторые (не все!) проблемы доверия, которые мы только что рассмотрели. Это доблестная, но обреченная попытка спасти шаблон обратного вызова от разрушения самого себя.
Например, что касается более изящной обработки ошибок, некоторые проекты API предусматривают раздельные обратные вызовы (один для уведомления об успехе, другой для уведомления об ошибке:
```js
function success(data) {
console.log( data );
}
function failure(err) {
console.error( err );
}
ajax( "http://some.url.1", success, failure );
```
В API такого дизайна часто обработчик ошибок `failure()` является необязательным, и если он не указан, предполагается, что вы хотите проглотить ошибки. Фу.
**Примечание.** Этот дизайн с разделенным обратным вызовом используется API ES6 Promise. Мы рассмотрим обещания ES6 более подробно в следующей главе.
Другой распространенный шаблон обратного вызова называется «стиль с ошибкой в первую очередь» (иногда его также называют «стилем узла», поскольку это также соглашение, используемое почти во всех API-интерфейсах Node.js), где первый аргумент одного обратного вызова зарезервирован для объекта ошибки (если есть). В случае успеха этот аргумент будет пустым/ложным (и любые последующие аргументы будут данными об успехе), но если сигнализируется результат ошибки, первый аргумент устанавливается/истинный (и обычно больше ничего не передается):
```js
function response(err,data) {
// ошибка?
if (err) {
console.error( err );
}
// в иных случаях успех
else {
console.log( data );
}
}
ajax( "http://some.url.1", response );
```
В обоих этих случаях следует соблюдать несколько вещей.
Во-первых, это на самом деле не решило большинство проблем с доверием, как может показаться. В обратном вызове нет ничего, что предотвращало бы или отфильтровывало нежелательные повторные вызовы. Более того, сейчас дела обстоят еще хуже, потому что вы можете получить как сигналы об успехе, так и ошибки, или ни одного из них, и вам все равно придется кодировать любое из этих условий.
Кроме того, не упустите тот факт, что, хотя это стандартный шаблон, который вы можете использовать, он определенно более многословен и похож на шаблон без особого повторного использования, поэтому вы устанете вводить все это для каждого обратного вызова в вашем приложении.
А как насчет проблемы доверия никогда не быть вызванным? Если это вызывает беспокойство (и, вероятно, должно быть!), вам, вероятно, потребуется установить тайм-аут, который отменяет событие. Вы можете сделать утилиту (показано только доказательство концепции), которая поможет вам в этом:
```js
function timeoutify(fn,delay) {
var intv = setTimeout( function(){
intv = null;
fn( new Error( "Timeout!" ) );
}, delay )
;
return function() {
// timeout уже произошёл?
if (intv) {
clearTimeout( intv );
fn.apply( this, [ null ].concat( [].slice.call( arguments ) ) );
}
};
}
```
Вот как можно его использовать:
```js
// использование обратного вызова в стиле «сначала ошибка»
function foo(err,data) {
if (err) {
console.error( err );
}
else {
console.log( data );
}
}
ajax( "http://some.url.1", timeoutify( foo, 500 ) );
```
Еще одна проблема доверия называется «слишком рано». С точки зрения конкретного приложения это может фактически включать вызов до завершения какой-либо критической задачи. Но в более общем плане проблема проявляется в утилитах, которые могут либо вызывать предоставленный вами обратный вызов *сейчас* (синхронно) или *позже* (асинхронно).
Этот недетерминизм в отношении синхронного или асинхронного поведения почти всегда приводит к тому, что очень трудно отследить ошибки. В некоторых кругах вымышленный вызывающий безумие монстр по имени Залго используется для описания синхронно-асинхронных кошмаров. «Не выпускайте Залго!» — это распространенный крик, и он приводит к очень здравому совету: всегда вызывайте обратные вызовы асинхронно, даже если это «сразу же» на следующем этапе цикла событий, чтобы все обратные вызовы предсказуемо были асинхронными.
**Примечание.** Дополнительную информацию о Zalgo см. в статье Орена Голана «Не выпускайте Zalgo!» (https://github.com/oren/oren.github.io/blob/master/posts/zalgo.md) и «Разработка API-интерфейсов для асинхронности» Исаака З. Шлютера (http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony).
Рассмотрим:
```js
function result(data) {
console.log( a );
}
var a = 0;
ajax( "..pre-cached-url..", result );
a++;
```
Будет ли этот код печатать «0» (вызов обратного вызова синхронизации) или «1» (вызов асинхронного обратного вызова)? Зависит... от условий.
Вы видите, как быстро непредсказуемость Zalgo может угрожать любой JS-программе. Таким образом, глупо звучащее «никогда не выпускайте Zalgo» на самом деле является невероятно распространенным и надежным советом. Всегда быть асинхронным.
Что, если вы не знаете, всегда ли рассматриваемый API будет выполняться асинхронно? Вы можете изобрести утилиту, подобную этой `asyncify(..)`, для доказательства концепции:
```js
function asyncify(fn) {
var orig_fn = fn,
intv = setTimeout( function(){
intv = null;
if (fn) fn();
}, 0 )
;
fn = null;
return function() {
// срабатывает слишком быстро, прежде чем сработает таймер
// `intv`, чтобы показать, что асинхронный ход прошел?
if (intv) {
fn = orig_fn.bind.apply(
orig_fn,
// добавляем `this` обёртки к `bind(..)`
// параметры вызова, а также каррирование любых
// передается в параметрах
[this].concat( [].slice.call( arguments ) )
);
}
// уже асинхронно
else {
// вызвать исходную функцию
orig_fn.apply( this, arguments );
}
};
}
```
Можно использовать `asyncify(..)` следующим образом:
```js
function result(data) {
console.log( a );
}
var a = 0;
ajax( "..pre-cached-url..", asyncify( result ) );
a++;
```
Независимо от того, находится ли запрос Ajax в кеше и разрешает попытку немедленного вызова обратного вызова, или его нужно получить по сети и, таким образом, выполнить позже асинхронно, этот код всегда будет выводить `1` вместо `0` -- `result(..)` не может не вызываться асинхронно, что означает, что `a++` имеет шанс запуститься раньше, чем `result(..)`.
Ура, еще одна проблема доверия решена! Но это неэффективно и еще больше раздувает шаблон, чтобы отягощать ваш проект.
Это просто история, снова и снова, с обратными вызовами. Они могут делать почти все, что вы хотите, но вы должны быть готовы много работать, чтобы получить это, и часто эти усилия намного больше, чем вы можете или должны потратить на такие рассуждения о коде.
Возможно, вам понадобятся встроенные API или другие языковые механизмы для решения этих проблем. Наконец-то появился ES6 с отличными ответами, так что продолжайте читать!
## Обзор
Обратные вызовы — это фундаментальная единица асинхронности в JS. Но их недостаточно для меняющегося ландшафта асинхронного программирования по мере взросления JS.
Во-первых, наш мозг планирует вещи последовательным, блокирующим, однопоточным семантическим способом, но обратные вызовы выражают асинхронный поток довольно нелинейным, непоследовательным образом, что значительно усложняет правильное рассмотрение такого кода. Плохие рассуждения о коде — это плохой код, который приводит к серьезным ошибкам.
Нам нужен способ выразить асинхронность в более синхронной, последовательной, блокирующей манере, как это делает наш мозг.
Во-вторых, что более важно, обратные вызовы страдают от *инверсии управления*, поскольку они неявно передают управление другой стороне (часто сторонней утилите, не находящейся под вашим контролем!) для вызова *продолжения* вашей программы. Эта передача управления приводит нас к тревожному списку проблем с доверием, например, вызывается ли обратный вызов чаще, чем мы ожидаем.
Изобретение специальной логики для решения этих проблем с доверием возможно, но это сложнее, чем должно быть, и в результате получается более громоздкий и трудный для сопровождения код, а также код, который, вероятно, недостаточно защищен от этих опасностей, пока вы явно не укусите его ошибки.
Нам нужно обобщенное решение **всех проблем с доверием**, которое можно будет повторно использовать для любого количества обратных вызовов, которое мы создадим, без дополнительных шаблонных накладных расходов.
Нам нужно что-то лучше, чем обратные вызовы. До сих пор они хорошо служили нам, но «будущее» JavaScript требует более сложных и эффективных асинхронных шаблонов. В последующих главах этой книги мы углубимся в эти зарождающиеся эволюции.
================================================
FILE: async & performance/ch3.md
================================================
# Вы не знаете JS: Асинхронность и производительность
# Глава 3: Промисы
В главе 2 мы определили две главные категории недостатков в использовании колбеков для того, чтобы выразить асинхронность программы и управлять параллельной обработкой: отсутствие последовательного кода и надежности. Теперь, когда мы разобрали эти проблемы досконально, настало время обратить внимание на шаблоны, которые позволят их решить.
Проблема, с которой мы хотим начать - *инверсия управления (IoC)*, это то доверие, которое так тяжело сохранять и так легко потерять.
Вспомните как мы обработали *продолжение (continuation)* нашей программы в колбек-функции и передали этот колбек в другую часть кода (потенциально даже во внешний код) и просто скрестили пальцы на удачу, что вызов этого колбэка произойдет корректно.
Мы сделали это поскольку мы хотели этим сказать, "вот то, что должно выполниться *позже*, после того, как завершится текущий шаг программы."
Но что если бы мы смогли разинверсировать эту *инверсию управления*? Что если вместо передачи продолжения программы в другой код мы могли бы ожидать от него дать нам возможность узнать когда его задача завершится и затем наш код мог бы решить, что делать дальше?
Эта концепция называется **Промисы**.
Промисы начинают быстро покорять мир JS, поскольку разработчики и создатели спецификаций в равной мере отчаянно ищут возможность избавиться от безумия ада колбеков в своим коде/дизайне. На самом деле, многие новые асинхронные API добавляются в платформу JS/DOM будучи построенным на промисах. Так что, возможно, это неплохая идея уйти с головой и изучить их, Вы так не думаете!?
**Примечание:** Слово "сразу" будет часто использоваться в этой главе, в основном указывая на какое-либо действие по разрешению промиса. Однако, в фактически всех случаях, "сразу" в терминах поведения очереди заданий означает (см. главу 1) не строго синхронное значение *сейчас*.
## Что такое промис?
Когда разработчики решают изучить новую технологию или шаблон, обычно их первым шагом будет "Покажите мне код!". Это вполне естественно для нас просто броситься в омут с головой и изучать по ходу.
Однако бывает так, что одних только API недостаточно, чтобы понять определённые абстракции. Промисы — это как раз тот инструмент, где по тому, как их используют, сразу видно: понимает ли человек, для чего они нужны и как работают, или просто изучил и использует API.
Так что перед тем как я покажу код с промисами, я хочу объяснить целиком и полностью что такое промисы концептуально. Я надеюсь, что это быстрее направит вас на истинный путь по мере того как вы будете постигать теорию интеграции промисов в свой асинхронный поток.
Помня об этом, давайте взглянем на две различные аналогии того что *есть* промисы.
### Будущее значение
Представьте такой сценарий: Я подхожу к стойке в ресторане быстрого питания и делаю заказ на чизбургер. Я даю кассиру $1.47. Разместив и оплатив свой заказ, я сделал запрос возврата *значения* (чизбургера). Я открыл транзакцию.
Но частенько, чизбургер не сразу мне доступен. Кассир даем мне что-то взамен моего чизбургера: чек с номером заказа в нем. Этот номер заказа - это IOU-*обещание* (промис) (Я должен вам ("I owe you")) которое гарантирует, что в итоге я должен получить свой чизбургер.
Так что я храню мой чек и номер заказа. Я знаю, что оно представляет собой мой *будущий чизбургер*, поэтому мне не надо о этом больше волноваться кроме ощущения голода!
Пока я жду Я могу заниматься другими делами, например отправить текстовое сообщение другу, говорящее: "Эй, как насчет присоединиться ко мне за обедом? Я собираюсь съесть чизбургер."
Я уже рассуждаю о своем *будущем чизбургере*, несмотря на то, что у меня еще его нет. Мой мозг способен так делать потому что он воспринимает номер заказа как заменитель чизбургера. Такой заменитель фактически делает значение *независимым от времени*. Это и есть **будущее значение**.
В итоге я слышу "Заказ 113!" и радостно иду обратно к стойке с чеком в руках. Я передаю чек кассиру и взамен беру свой чизбургер.
Другими словами, как только мое *будущее значение* было готово, я обменял промис значения на само значение.
Но есть и еще один возможный исход. Они называют мой номер заказа, но когда я подхожу, чтобы забрать свой чизбургер кассир с сожалению сообщает мне: "Мне жаль, но мы судя по всему остались без чизбургеров." Оставив в стороне разочарование покупателя в таком сценарии на секундочку, мы можем заметить важную характеристику *будущих значений*: они могут сигнализировать либо о завершении, либо об отказе.
Каждый раз, когда я заказываю чизбургер, я знаю что либо я рано или поздно получу чизбургер, либо получу печальные новости о нехватке чизбургеров и я должен буду придумать что-то другое на обед.
**Примечание:** В реальном коде процесс не такой простой, потому что метафорически номер заказа может никогда не быть назван, и в таком случае мы остаемся в неразрешим состоянии вечно. Мы еще вернемся к этому случаю позже.
#### Значения Сейчас и Позже
Всё это может прозвучать слишком абстрактно для применения в вашем коде. Так давайте внесем больше конкретики.
Однако, перед тем, как мы представим как работают промисы подобным образом, мы собираемся покопаться в коде, который мы уже умеем понимать -- колбеки! -- чтобы понять как обрабатывать эти *будущие значения*.
Когда вы пишете код, чтобы обработать каким-либо образом некое значение, например выполнив математические вычисления над `числом`, то осознанно или нет, вы предполагаете кое-что очень фундаментальное об этом значении, а именно, что это - уже конкретное значение *сейчас*:
```js
var x, y = 2;
console.log( x + y ); // NaN <-- потому что в `x` еще не установлено значение
```
Операция `x + y` предполагает, что оба `x` и `y` уже заданы. В терминах, которые мы вскоре разъясним, мы полагаем что значения `x` и `y` уже *разрешены* (т.е. с уж определенными значениями).
Будет абсурдом ожидать, что оператор `+` сам по себе каким-то магическим образом сможет определить и ждать до тех пор, пока оба `x` и `y` разрешатся (т.е. будут готовы), и только затем выполнит операцию. Это может привести к хаосу в программе, если одни выражения закончатся *сейчас*, а другие закончатся *позже*, не так ли?
Как вы сможете потенциально рассуждать о связях между двумя выражениями, если одно из них (или оба) могут быть еще не завершены? Если выражение 2 полагается на то, что выражение 1 будет завершено, то возможны два исхода: либо выражение 1 закончится прямо *сейчас* и всё благополучно продолжится, либо statement 1 еще не завершено, и в итоге выражение 2 приведет к ошибке.
Если такие вещи звучат знакомо после главы 1, хорошо!
Давайте вернемся к нашей математической операции `x + y`. Представьте, что был бы путь сказать, "Сложи `x` и `y`, но если кто-то из них еще не подготовлен, просто подожди пока это не произойдет. Сложи их как можно скорее."
Ваш мозг возможно сразу переключился на колбеки. Хорошо, итак...
```js
function add(getX,getY,cb) {
var x, y;
getX( function(xVal){
x = xVal;
// оба готовы?
if (y != undefined) {
cb( x + y ); // отправить сумму
}
} );
getY( function(yVal){
y = yVal;
// оба готовы?
if (x != undefined) {
cb( x + y ); // отправить сумму
}
} );
}
// `fetchX()` and `fetchY()` синхронные или асинхронные
// функции
add( fetchX, fetchY, function(sum){
console.log( sum ); // это было легко, ага?
} );
```
Подождите минутку, чтобы позволить красоте (или отсутствию таковой) этого кусочка кода уложиться в голове (терпеливо насвистываю).
Хотя это уродство и несомненное, тут есть кое-что очень важное на заметку об этом асинхронном шаблоне.
В этом кусочке кода, мы трактовали `x` и `y` как будущие значения и мы выразили операцию `add(..)` так, что она (снаружи) не заботится о том, доступен ли `x` или `y` прямо сейчас или нет. Другими словами, он нормализует *сейчас* и *потом* таким образом, что мы можем положиться на предсказуемый результат операции `add(..)`.
При использовании `add(..)`, которая временно консистентна, она ведет себя одинаково *сейчас* и *потом* - такой асинхронный код легче себе представлять.
Выражаясь более просто: чтобы обработать согласованно оба *сейчас* и *потом*, мы оба их превращаем в *потом*: все операции становятся асинхронными.
Конечно, этот грубый подход, основанный на колбеках, оставляет желать намного лучшего. Это всего-лишь первый крошечный шаг к пониманию преимуществ представления *будущих значений* без беспокойств о временном аспекте того, доступны они или нет.
#### Промис как значение
Мы определенно углубимся в детали промисов позже в этой главе, поэтому не волнуйтесь если что-то тут покажется запутанным, а просто мельком взгляните на то, как мы выразим пример `x + y` через `Promise`ы:
```js
function add(xPromise,yPromise) {
// `Promise.all([ .. ])` принимает массив промисов
// и возвращает новый промис, который ожидает завершения всех переданных
return Promise.all( [xPromise, yPromise] )
// когда промис разрешен, давайте возьмем
// полученные значения `X` и `Y` и сложим их.
.then( function(values){
// `values` - массив сообщений от
// ранее разрешенных промисов
return values[0] + values[1];
} );
}
// `fetchX()` и `fetchY()` возвращают промисы для
// своих соответствующих значений, которые могут быть готовы
// *сейчас* или *позже*.
add( fetchX(), fetchY() )
// мы получаем обратно промис с суммой этих
// двух чисел.
// теперь мы выполняем в цепочке вызов `then(..)`, чтобы дождаться разрешения
// этого возвращенного промиса.
.then( function(sum){
console.log( sum ); // это намного легче!
} );
```
В этом кусочке кода есть два слоя промисов.
`fetchX()` и `fetchY()` вызываются напрямую и возвращаемые или значения (промисы!) передаются в `add(..)`. Внутренние значения, которые представляют эти промисы, могут быть готовы *сейчас* или *позже*, но каждый промис приводит свое поведение к тому, чтобы вести себя одинаково вне зависимости ни о чего. Мы рассуждаем о значениях `X` и `Y` во время-независимой манере. Они - *будущие значения*.
Второй уровень - это промис, который создается в `add(..)` (через `Promise.all([ .. ])`) и возвращается, и который мы ожидаем вызвав `then(..)`. Когда операция `add(..)` завершена, наше *будущее значение* `sum` готово и можем вывести его на экран. Внутри `add(..)` мы скрываем всю логику ожидания *будущих значений* `X` и `Y`.
**Примечание** Внутри `add(..)`, вызов `Promise.all([ .. ])` создает промис (который ждем разрешения `promiseX` и `promiseY`). Цепочечный Вызов `.then(..)` создает еще один промис, который сразу же разрешает строку `return values[0] + values[1]` (с результатом сложения). Таким образом, вызов `then(..)`, который мы поместили в конец цепочки вызова `add(..)` в конце фрагмента кода, в сущности работает с этим вторым возвращенным промисом, а не с первым, созданным `Promise.all([ .. ])`. Также, хотя мы и не добавили ничего в конец цепочки второго `then(..)`, он также создает еще один промис, невзирая на то, хотим мы его использовать или нет. Эту штуку с цепочками промисов мы поясним в деталях позже в этой главе.
Прямо как с заказами чизбургеров, есть такая же вероятность того, что промис разрешится отказом вместо исполнения. В отличие от исполненного промиса, где значение всегда программно задано, значение при отказе, обычно называемое "причиной отказа", может быть установлено либо напрямую в логике программы, либо может явиться результатом исключения времени выполнения.
С использованием промисов, вызов `then(..)` фактически может принимать две функции: первую - для завершения (как показано ранее), а вторую - для отказа:
```js
add( fetchX(), fetchY() )
.then(
// обработчик завершения
function(sum) {
console.log( sum );
},
// обработчик отказа
function(err) {
console.error( err ); // облом!
}
);
```
Если что-то пошло не так при получении `X` или `Y`, или что-то каким-либо образом привело к сбою во время сложения, промис, который возвращается из `add(..)` - отвергается (завершается отказом) и второй колбек-обработчик ошибок, переданный в `then(..)` получит значение отказа из промиса.
Поскольку промисы инкапсулируют в себе состояние, не зависящее от времени, с ожиданием завершения или отказа получения значения операции снаружи, промис сам по себе является независимым от времени и потому промисы можно компоновать (составлять) предсказуемым образом независимо от времени или внутреннего результата.
Более того, как только промис разрешен, он остается таковым навсегда, он становится *неизменяемым значением* в этот момент и может потом быть *обследован* столько раз, сколько нужно.
**Примечание** Поскольку промис является неизменяемым внешне как только он разрешен, то теперь можно безопасно передавать его куда угодно зная, что он не может быть изменен случайно или злонамеренно. Это особенно верно в связи с тем, что наблюдать за разрешением одного и того же промиса могут разные стороны. Невозможно повлиять на возможность одной стороны наблюдать за разрешением промиса другой стороной. Неизменяемость может прозвучать как какая-то научная тема, но на самом деле это один из самых фундаментальных и важных аспектов дизайна промисов и не должен быть рассмотрен походя мимоходом.
Это один из самых мощных и важных ключей к пониманию промисов. Проделав достаточно большую работу, вы могли бы специально добиться того же эффекта используя только композицию из уродливых колбеков, но это не особенно эффективная стратегия, особенно потому, что вы вынуждены делать это снова и снова.
Промисы - это легко повторяемый механизм инкапсуляции и совмещения *будущих значений*.
### Событие завершения
Как мы только что видели, одиночный промис ведет себя как *будущее значение*. Но есть и другой путь представлять разрешение промиса: как механизм потокового управления, временнОе "это-затем-то" для двух и более шагов в асинхронной задаче.
Давайте представим вызов функции `foo(..)` для выполнения некой задачи. Мы либо не знаем ничего о ее внутренней реализации, или просто не беспокоимся об этом. Она может завершить задачу сразу или может занять некоторое время.
Нам просто нужно знать когда завершится `foo(..)`, чтобы мы могли двигаться к нашей следующей задаче. Другими словами, нам нужна возможность получить оповещение о завершении `foo(..)`, чтобы мы могли *продолжить* выполнение.
В обычном случае в JavaScript если вам нужно получить оповещение, вы вероятно подумаете об этом с точки зрения событий. Таким образом мы можем переформулировать нашу потребность в оповещении как необходимость получить событие *завершения* (или *продолжения*), инициированное функцией `foo(..)`.
**Примечание** Назовете ли вы это "событием завершения" или "событием продолжения" зависит от вашей точки зрения. На чем больше смещен фокус: на том что случится в `foo(..)` или на том что произойдет *после* завершения `foo(..)`? Обе точки зрения точны и полезны. Уведомление о событии сообщает нам, что `foo(..)` *завершилась*, но также и то, что можно *продолжить* выполнение следующего шага. Безусловно, тот колбек, который вы передаете, чтобы он был вызван для уведомления о событии, сам по себе то, что мы ранее назвали *продолжение*. Потому что *событие завершения* немного более сфокусировано на `foo(..)`, что больше привлекает наше внимание в настоящий момент, мы все же чуть больше отдаем предпочтение *событию завершения* до конца этого текста.
С использованием колбеков, "уведомлением" будет наш колбек, вызванный задачей (`foo(..)`). Но с промисами, мы переворачиваем отношения и ожидаем, что можем ждать событие от `foo(..)` и как только получим его может действовать соответственно.
Сперва, обратите внимание на этот псевдокод:
```js
foo(x) {
// начинаем выполнять что-то, что требует времени
}
foo( 42 )
on (foo "completion") {
// теперь мы можем выполнить следующий шаг!
}
on (foo "error") {
// ой, что-то пошло не так в `foo(..)`
}
```
Мы вызываем `foo(..)`, а затем настраиваем два обработчика событий, один для `"completion"` (завершение), а другой для `"error"` (сбоя)-- двух возможных *окончательных* исхода вызова `foo(..)`. По сути, не похоже, что `foo(..
gitextract_bdk32l8f/
├── .gitignore
├── LICENSE.txt
├── README.md
├── async & performance/
│ ├── README.md
│ ├── apA.md
│ ├── apB.md
│ ├── apC.md
│ ├── ch1.md
│ ├── ch2.md
│ ├── ch3.md
│ ├── ch4.md
│ ├── ch5.md
│ ├── ch6.md
│ ├── foreword.md
│ └── toc.md
├── es6 & beyond/
│ ├── README.md
│ ├── apA.md
│ ├── ch1.md
│ ├── ch2.md
│ ├── ch3.md
│ ├── ch4.md
│ ├── ch5.md
│ ├── ch6.md
│ ├── ch7.md
│ ├── ch8.md
│ ├── foreword.md
│ └── toc.md
├── preface.md
├── scope & closures/
│ ├── README.md
│ ├── apA.md
│ ├── apB.md
│ ├── apC.md
│ ├── apD.md
│ ├── ch1.md
│ ├── ch2.md
│ ├── ch3.md
│ ├── ch4.md
│ ├── ch5.md
│ └── toc.md
├── this & object prototypes/
│ ├── README.md
│ ├── apA.md
│ ├── apB.md
│ ├── ch1.md
│ ├── ch2.md
│ ├── ch3.md
│ ├── ch4.md
│ ├── ch5.md
│ ├── ch6.md
│ ├── foreword.md
│ └── toc.md
├── types & grammar/
│ ├── README.md
│ ├── apA.md
│ ├── apB.md
│ ├── ch1.md
│ ├── ch2.md
│ ├── ch3.md
│ ├── ch4.md
│ ├── ch5.md
│ ├── foreword.md
│ └── toc.md
└── up & going/
├── README.md
├── apA.md
├── ch1.md
├── ch2.md
├── ch3.md
├── foreword.md
└── toc.md
Condensed preview — 67 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,747K chars).
[
{
"path": ".gitignore",
"chars": 0,
"preview": ""
},
{
"path": "LICENSE.txt",
"chars": 17748,
"preview": "Creative Commons\nAttribution-NonCommercial-NoDerivs 3.0 Unported\n\n CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND"
},
{
"path": "README.md",
"chars": 5216,
"preview": "# Вы не знаете JS (серия книг)\n\nПредставляю вам серию книг, погружение в которую позволит вам окунуться в основные механ"
},
{
"path": "async & performance/README.md",
"chars": 741,
"preview": "# Вы не знаете JS: Асинхронность и Производительность\n\n<img src=\"cover.jpg\" width=\"300\">\n\n-----\n\n**[Купить цифровую или "
},
{
"path": "async & performance/apA.md",
"chars": 33018,
"preview": "# You Don't Know JS: Async & Performance\n# Appendix A: *asynquence* Library\n\nChapters 1 and 2 went into quite a bit of d"
},
{
"path": "async & performance/apB.md",
"chars": 34931,
"preview": "# You Don't Know JS: Async & Performance\n# Appendix B: Advanced Async Patterns\n\nAppendix A introduced the *asynquence* l"
},
{
"path": "async & performance/apC.md",
"chars": 9646,
"preview": "# Вы не знаете JS: Асинхронность и Производительность\n# Приложение C: Благодарности\n\nЕсть множество людей, которых нужно"
},
{
"path": "async & performance/ch1.md",
"chars": 43588,
"preview": "# Вы не знаете JS: Асинхронность и Выполнение\n# Глава 1: Асинхронность: Сейчас и Потом\n\nУправление программой в течении "
},
{
"path": "async & performance/ch2.md",
"chars": 37767,
"preview": "# Вы не знаете JS: Асинхронность и Выполнение\n# Глава 2: Колбеки\n\nВ главе 1 мы рассмотрели терминологию и концепции асин"
},
{
"path": "async & performance/ch3.md",
"chars": 120531,
"preview": "# Вы не знаете JS: Асинхронность и производительность\n# Глава 3: Промисы\n\nВ главе 2 мы определили две главные категории "
},
{
"path": "async & performance/ch4.md",
"chars": 94675,
"preview": "# You Don't Know JS: Async & Performance\n# Chapter 4: Generators\n\nIn Chapter 2, we identified two key drawbacks to expre"
},
{
"path": "async & performance/ch5.md",
"chars": 29649,
"preview": "# You Don't Know JS: Async & Performance\n# Глава 5: Производительность программы\n\nДо сих пор эта книга была посвящена то"
},
{
"path": "async & performance/ch6.md",
"chars": 47818,
"preview": "# You Don't Know JS: Async & Performance\n# Chapter 6: Benchmarking & Tuning\n\nAs the first four chapters of this book wer"
},
{
"path": "async & performance/foreword.md",
"chars": 2524,
"preview": "# Вы не знаете JS: Асинхронность и Производительность\n# Введение\n\nМой работодатель доверяет мне проводить собеседования "
},
{
"path": "async & performance/toc.md",
"chars": 1070,
"preview": "# Вы не знаете JS: Асинхронность и Производительность\n\n## Содержимое\n\n* Предисловие\n* Preface\n* Chapter 1: Asynchrony: N"
},
{
"path": "es6 & beyond/README.md",
"chars": 713,
"preview": "# Вы не знаете JS: ES6 и не только\n\n<img src=\"cover.jpg\" width=\"300\">\n\n-----\n\n**[Купить цифровую или печатную книгу от и"
},
{
"path": "es6 & beyond/apA.md",
"chars": 9627,
"preview": "# Вы не знаете JS: ES6 и не только\n# Приложение A: Благодарности\n\nЕсть множество людей, которых нужно поблагодарить за т"
},
{
"path": "es6 & beyond/ch1.md",
"chars": 10307,
"preview": "# Вы не знаете JS: ES6 и не только\n# Глава 1: ES: современность и будущее\n\nДля чтения этой книги вы должны хорошо владет"
},
{
"path": "es6 & beyond/ch2.md",
"chars": 120414,
"preview": "# You Don't Know JS: ES6 & Beyond\n# Chapter 2: Syntax\n\nIf you've been writing JS for any length of time, odds are the sy"
},
{
"path": "es6 & beyond/ch3.md",
"chars": 94270,
"preview": "# You Don't Know JS: ES6 & Beyond\n# Chapter 3: Organization\n\nIt's one thing to write JS code, but it's another to proper"
},
{
"path": "es6 & beyond/ch4.md",
"chars": 17795,
"preview": "# You Don't Know JS: ES6 & Beyond\n# Chapter 4: Async Flow Control\n\nIt's no secret if you've written any significant amou"
},
{
"path": "es6 & beyond/ch5.md",
"chars": 20499,
"preview": "# You Don't Know JS: ES6 & Beyond\n# Chapter 5: Collections\n\nStructured collection and access to data is a critical compo"
},
{
"path": "es6 & beyond/ch6.md",
"chars": 32217,
"preview": "# You Don't Know JS: ES6 & Beyond\n# Chapter 6: API Additions\n\nFrom conversions of values to mathematic calculations, ES6"
},
{
"path": "es6 & beyond/ch7.md",
"chars": 62772,
"preview": "# You Don't Know JS: ES6 & Beyond\n# Chapter 7: Meta Programming\n\nMeta programming is programming where the operation tar"
},
{
"path": "es6 & beyond/ch8.md",
"chars": 22568,
"preview": "# You Don't Know JS: ES6 & Beyond\n# Chapter 8: Beyond ES6\n\nAt the time of this writing, the final draft of ES6 (*ECMAScr"
},
{
"path": "es6 & beyond/foreword.md",
"chars": 2085,
"preview": "# You Don't Know JS: ES6 & Beyond\n# Foreword\n\nKyle Simpson is a thorough pragmatist.\n\nI can't think of higher praise tha"
},
{
"path": "es6 & beyond/toc.md",
"chars": 1244,
"preview": "# You Don't Know JS: ES6 & Beyond\n\n## Table of Contents\n\n* Предисловие\n* Введение\n* Глава 1: ES? Настоящее и Будущее\n\t* "
},
{
"path": "preface.md",
"chars": 5272,
"preview": "# Вы не знаете JS\n# Предисловие\n\nЯ уверен, что вы заметили, но «JS» в названии серии книг — это не аббревиатура для слов"
},
{
"path": "scope & closures/README.md",
"chars": 870,
"preview": "# Вы не знаете JS: Область видимости и замыкания\n\n<img src=\"cover.jpg\" width=\"300\">\n\n-----\n\n**[Купить цифровую или печат"
},
{
"path": "scope & closures/apA.md",
"chars": 3635,
"preview": "# Вы не знаете JS: Область видимости и замыкания\n# Приложение A: Динамическая область видимости\n\nВ главе 2 мы говорили о"
},
{
"path": "scope & closures/apB.md",
"chars": 7090,
"preview": "# Вы не знаете JS: Область видимости и замыкания\n# Приложение B: Полифиллинг блочной области видимости\n\nВ главе 3 мы исс"
},
{
"path": "scope & closures/apC.md",
"chars": 4647,
"preview": "# Вы не знаете JS: Область видимости и замыкания\n# Приложение C: Лексический this\n\nХотя эта книга и не рассматривает мех"
},
{
"path": "scope & closures/apD.md",
"chars": 9643,
"preview": "# Вы не знаете JS: Область видимости и замыкания\n# Приложение D: Благодарности\n\nЕсть множество людей, которых нужно побл"
},
{
"path": "scope & closures/ch1.md",
"chars": 21181,
"preview": "# Вы не знаете JS: Область видимости и замыкания\n# Глава 1: Что такое область видимости?\n\nОдна из самых фундаментальных "
},
{
"path": "scope & closures/ch2.md",
"chars": 17817,
"preview": "# Вы не знаете JS: Область видимости и замыкания\n# Глава 2: Лексическая область видимости\n\nВ главе 1, мы определили \"обл"
},
{
"path": "scope & closures/ch3.md",
"chars": 30063,
"preview": "# Вы не знаете JS: Область видимости и замыкания\n# Глава 3: Область видимости: функции против блоков\n\nКак мы уже исследо"
},
{
"path": "scope & closures/ch4.md",
"chars": 8129,
"preview": "# Вы не знаете JS: Область видимости и замыкания\n# Глава 4: Поднятие переменных (Hoisting)\n\nТеперь вы должно быть вполне"
},
{
"path": "scope & closures/ch5.md",
"chars": 29439,
"preview": "# Вы не знаете JS: Область видимости и замыкания\n# Глава 5: Замыкание области видимости\n\nМы добрались до этого места, ка"
},
{
"path": "scope & closures/toc.md",
"chars": 922,
"preview": "# Вы не знаете JS: Область видимости и замыкания\n\n## Содержание\n\n* Предисловие\n* Введение\n* Глава 1: Что такое область в"
},
{
"path": "this & object prototypes/README.md",
"chars": 674,
"preview": "# Вы не знаете JS: *this* и Прототипы Объектов\n\n<img src=\"cover.jpg\" width=\"300\">\n\n-----\n\n**[Купить цифровую или печатну"
},
{
"path": "this & object prototypes/apA.md",
"chars": 13541,
"preview": "# Вы не знаете JS: This и прототипы объектов\n# Приложение А: `class` в ES6\n\nЕсли что-то можно вынести из второй половины"
},
{
"path": "this & object prototypes/apB.md",
"chars": 9639,
"preview": "# Вы не знаете JS: *this* и Прототипы Объектов\n# Приложение B: Благодарности\n\nЕсть множество людей, которых нужно поблаг"
},
{
"path": "this & object prototypes/ch1.md",
"chars": 13677,
"preview": "# Вы не знаете JS: This и Прототипы Объектов\n# Глава 1: `this` (этот) или That (тот)?\n\nОдним из наиболее запутанных меха"
},
{
"path": "this & object prototypes/ch2.md",
"chars": 38039,
"preview": "# Вы не знаете JS: *this* и прототипы объектов\n# Глава 2: Весь `this` теперь приобретает смысл!\n\nВ главе 1 мы отбросили "
},
{
"path": "this & object prototypes/ch3.md",
"chars": 46561,
"preview": "# Вы не знаете JS: *this* и прототипы объектов\n# Глава 3: Объекты\n\nВ первых двух главах мы объяснили как `this` указывае"
},
{
"path": "this & object prototypes/ch4.md",
"chars": 36628,
"preview": "# Вы не знаете JS: This и Прототипы Объектов\n# Глава 4: Смешивая объекты \"классов\"\n\nПродолжая исследование объектов, нач"
},
{
"path": "this & object prototypes/ch5.md",
"chars": 49876,
"preview": "# # Вы не знаете JS: _this_ и прототипы объектов\r\n\r\n# Глава 5: Прототипы\r\n\r\nВ главах 3 и 4 мы неоднократно упоминали цеп"
},
{
"path": "this & object prototypes/ch6.md",
"chars": 48981,
"preview": "# \r\n# Вы не знаете JS: *this* и прототипы объектов\r\n# Глава 6: Делегирование поведения\r\n\r\nВ главе 5 мы подробно изучили "
},
{
"path": "this & object prototypes/foreword.md",
"chars": 3319,
"preview": "# Вы не знаете JS: *this* и Прототипы Объектов\n# Введение\n\nКогда я читал эту книгу и готовился написать введение, я заду"
},
{
"path": "this & object prototypes/toc.md",
"chars": 779,
"preview": "# You Don't Know JS: *this* & Object Prototypes\n\n## Table of Contents\n\n* Foreword\n* Preface\n* Chapter 1: `this` Or That?"
},
{
"path": "types & grammar/README.md",
"chars": 573,
"preview": "# Вы не знаете JS: Типы и грамматика\n\n<img src=\"cover.jpg\" width=\"300\">\n\n-----\n\n**[Купить цифровую или печатную книгу от"
},
{
"path": "types & grammar/apA.md",
"chars": 22694,
"preview": "# You Don't Know JS: Types & Grammar\n# Appendix A: Mixed Environment JavaScript\n\nBeyond the core language mechanics we'v"
},
{
"path": "types & grammar/apB.md",
"chars": 9629,
"preview": "# Вы не знаете JS: Типы и грамматика\n# Приложение B: Благодарности\n\nЕсть множество людей, которых нужно поблагодарить за"
},
{
"path": "types & grammar/ch1.md",
"chars": 16744,
"preview": "# Вы не знаете JS: Типы и грамматика\n# Глава 1: Типы\n\nБольшинство разработчиков скажут, что динамический язык (как JS) н"
},
{
"path": "types & grammar/ch2.md",
"chars": 45836,
"preview": "# Вы не знаете JS: Типы и грамматика\n# Глава 2: Типы\n\n`array`, `string`, и `number` являются основными составными элемен"
},
{
"path": "types & grammar/ch3.md",
"chars": 26980,
"preview": "# Вы не знаете JS: Типы и грамматика\n# Глава 3: Стандартные встроенные объекты\n\nНесколько раз в первой и второй главах э"
},
{
"path": "types & grammar/ch4.md",
"chars": 106492,
"preview": "# Вы не знаете JS: Типы и грамматика\n# Глава 4: Приведение\n\nТеперь, когда мы гораздо более полно понимаем типы и значени"
},
{
"path": "types & grammar/ch5.md",
"chars": 63887,
"preview": "# Вы не знаете JS: Типы и грамматика\n# Глава 5: Грамматика\n\nПоследняя важная тема, которую мы хотим затронуть, - это то,"
},
{
"path": "types & grammar/foreword.md",
"chars": 2527,
"preview": "# Вы не знаете JS: Типы и грамматика\n# Предисловие\n\nКак было однажды сказано, \"JavaScript — это единственный язык, котор"
},
{
"path": "types & grammar/toc.md",
"chars": 776,
"preview": "# Вы не знаете JS: Типы и грамматика\n\n## Содержание\n\n* Предисловие\n* Введение\n* Глава 1: Типы\n\t* Тип, как бы он ни назыв"
},
{
"path": "up & going/README.md",
"chars": 532,
"preview": "# Вы не знаете JS: Начните и Совершенствуйтесь\n\n<img src=\"cover.jpg\" width=\"300\">\n\n-----\n\n**[Купить цифровую или печатну"
},
{
"path": "up & going/apA.md",
"chars": 9856,
"preview": "# Вы не знаете JS: Начните и Совершенствуйтесь\n# Приложение A: Благодарности!\n\nЕсть много людей, которых я хотел бы побл"
},
{
"path": "up & going/ch1.md",
"chars": 45410,
"preview": "# Вы не знаете JS: Начните и Совершенствуйтесь\n# Глава 1: Введение в программирование\n\nДобро пожаловать в серию книг *Вы"
},
{
"path": "up & going/ch2.md",
"chars": 47378,
"preview": "# Вы не знаете JS: Начните и Совершенствуйтесь\n# Глава 2: Введение в JavaScript\n\nВ предыдущей главе я представил основны"
},
{
"path": "up & going/ch3.md",
"chars": 13941,
"preview": "# Вы не знаете JS: Начните и Совершенствуйтесь\n# Глава 3: Введение в \"Вы не знаете JS\"\n\nО чем вся эта серия? Проще говор"
},
{
"path": "up & going/foreword.md",
"chars": 3852,
"preview": "# Вы не знаете JS: Начните и Совершенствуйтесь\n# Введение\n\nЧто нового вы изучили в последнее время?\n\nВозможно, это был и"
},
{
"path": "up & going/toc.md",
"chars": 689,
"preview": "# Вы не знаете JS: Начните и Совершенствуйтесь\n\n## Содержание\n\n* Предисловие\n* Введение\n* Глава 1: Введение в программир"
}
]
About this extraction
This page contains the full source code of the azat-io/you-dont-know-js-ru GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 67 files (1.6 MB), approximately 442.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.