async/await
. At each stage, asynchronous programming in Javascript was somewhat simplified for those who were already knee-deep in their own way in this language, but for beginners it only became more frightening because they needed to understand the nuances of each paradigm, mastering the use of each and, no less importantly, understand how it all works. getUserData(function doStuff(e, a) { getMoreUserData(function doMoreStuff(e, b) { getEvenMoreUserData(function doEvenMoreStuff(e, c) { getYetMoreUserData(function doYetMoreStuff(e, c) { console.log('Welcome to callback hell!'); }); }); }); })
do*Stuff
other functions ( get*UserData()
), to which you may not have the source code, and you cannot have are sure if they are doing your callback. Great, isn't it? getUserData() .then(getUserData) .then(doMoreStuff) .then(getEvenMoreUserData) .then(doEvenMoreStuff) .then(getYetMoreUserData) .then(doYetMoreStuff);
// , fetchJson(), GET , // : , – // . function fetchJson(url, callback) { ... } fetchJson('/api/user/self', function(e, user) { fetchJson('/api/interests?userId=' + user.id, function(e, interests) { var recommendations = []; interests.forEach(function () { fetchJson('/api/recommendations?topic=' + interest, function(e, recommendation) { recommendations.push(recommendation); if (recommendations.length == interests.length) { render(profile, interests, recommendations); } }); }); }); });
fetchJson()
method to return a promise, rather than accept a callback. Promis is resolved by the response body parsed in JSON format. fetchJson('/api/user/self') .then(function (user) { return fetchJson('/api/user/interests?userId=' + self.id); }) .then(function (interests) { return Promise.all[interests.map(i => fetchJson('/api/recommendations?topic=' + i))]; }) .then(function (recommendations) { render(user, interests, recommendations); });
fetchJson('/api/user/self') .then(function (user) { return fetchJson('/api/user/interests?userId=' + self.id) .then(interests => { user: user, interests: interests }); }) .then(function (blob) { return Promise.all[blob.interests.map(i => fetchJson('/api/recommendations?topic=' + i))] .then(recommendations => { user: blob.user, interests: blob.interests, recommendations: recommendations }); }) .then(function (bigBlob) { render(bigBlob.user, bigBlob.interests, bigBlob.recommendations); });
// , var user, interests; fetchJson('/api/user/self') .then(function (fetchedUser) { user = fetchedUser; return fetchJson('/api/user/interests?userId=' + self.id); }) .then(function (fetchedInterests) { interests = fetchedInterests; return Promise.all(interests.map(i => fetchJson('/api/recommendations?topic=' + i))); }) .then(function (recomendations) { render(user, interests, recommendations); }) .then(function () { console.log('We are done!'); });
fetchedUser
and fetchedInterests
, not user
and interests
? If so, then you are very observant! co(function* () { var user = yield fetchJson('/api/user/self'); var interests = yield fetchJson('/api/user/interests?userId=' + self.id); var recommendations = yield Promise.all( interests.map(i => fetchJson('/api/recommendations?topic=' + i))); render(user, interests, recommendations); });
function* counts(start) { yield start + 1; yield start + 2; yield start + 3; return start + 4; } const counter = counts(0); console.log(counter.next()); // {value: 1, done: false} console.log(counter.next()); // {value: 2, done: false} console.log(counter.next()); // {value: 3, done: false} console.log(counter.next()); // {value: 4, done: true} console.log(counter.next()); // {value: undefined, done: true}
const counter = counts();
- we initialize the generator and save it in the counter variable. The generator is in a suspended state, no code in the body of the generator has yet been executed.console.log(counter.next());
- issuance ( yield
) 1 is interpreted, after which 1 is returned as value
, and done
results in false
, because the output does not end thereconsole.log(counter.next());
- Now 2!console.log(counter.next());
- Now 3! Finished. Is that right? Not. Execution is suspended in step yield 3;
To complete, call next () again.console.log(counter.next());
- Now 4, and it returns, but not issued, so now we exit the function, and everything is ready.console.log(counter.next());
- Generator job finished! He has nothing to say except "everything is done." function* printer() { console.log("We are starting!"); console.log(yield); console.log(yield); console.log(yield); console.log("We are done!"); } const counter = printer(); counter.next(1); // ! counter.next(2); // 2 counter.next(3); // 3 counter.next(4); // 4\n ! counter.next(5); //
next
. It not only returns values ​​from the generator, but can also return them to the generator. If you give the next()
argument, then the yield
operation that the generator is now waiting for actually results in the argument. That is why the first counter.next(1)
registered as undefined
. There is simply no issue that could be allowed.co()
, right?next(value? : any)
method, with which we order the generator to continue working and give it values, another throw(error)
method in case an throw(error)
generated instead of a value, and finally return()
, which while we keep silent. If compliance with the interface is achieved, then everything is fine.counts()
generator on pure ES5, without the function*
keyword. For now, you can ignore throw()
and pass the value to next()
, because the method does not accept any input. How to do it? function makeCounter() { var count = 1; return function () { return count++; } } var counter = makeCounter(); console.log(counter()); // 1 console.log(counter()); // 2 console.log(counter()); // 3
counts()
, which returns 4 values ​​and terminates. What is needed for a universal approach to writing generator-like functions? function counts(start) { let state = 0; let done = false; function go() { let result; switch (state) { case 0: result = start + 1; state = 1; break; case 1: result = start + 2; state = 2; break; case 2: result = start + 3; state = 3; break; case 3: result = start + 4; done = true; state = -1; break; default: break; } return {done: done, value: result}; } return { next: go } } const counter = counts(0); console.log(counter.next()); // {value: 1, done: false} console.log(counter.next()); // {value: 2, done: false} console.log(counter.next()); // {value: 3, done: false} console.log(counter.next()); // {value: 4, done: true} console.log(counter.next()); // {value: undefined, done: true}
function printer(start) { let state = 0; let done = false; function go(input) { let result; switch (state) { case 0: console.log("We are starting!"); state = 1; break; case 1: console.log(input); state = 2; break; case 2: console.log(input); state = 3; break; case 3: console.log(input); console.log("We are done!"); done = true; state = -1; break; default: break; return {done: done, value: result}; } } return { next: go } } const counter = printer(); counter.next(1); // ! counter.next(2); // 2 counter.next(3); // 3 counter.next(4); // 4 counter.next(5); // !
input
as an argument to go
, and values ​​are piped. Does it look like magic again? Almost like generators? function* adder(initialValue) { let sum = initialValue; while (true) { sum += yield sum; } }
next(value)
to sum
, and then returns sum. It works exactly as we expected: const add = adder(0); console.log(add.next()); // 0 console.log(add.next(1)); // 1 console.log(add.next(2)); // 3 console.log(add.next(3)); // 6
function adder(initialValue) { let state = 'initial'; let done = false; let sum = initialValue; function go(input) { let result; switch (state) { case 'initial': result = initialValue; state = 'loop'; break; case 'loop': sum += input; result = sum; state = 'loop'; break; default: break; } return {done: done, value: result}; } return { next: go } } function runner() { const add = adder(0); console.log(add.next()); // 0 console.log(add.next(1)); // 1 console.log(add.next(2)); // 3 console.log(add.next(3)); // 6 } runner();
next()
will make the exception penetrate to the caller and the generator will die. Passing an exception to the generator is done in the throw()
method, which we omitted above. function* adder(initialValue) { let sum = initialValue; let lastSum = initialValue; let temp; while (true) { try { temp = sum; sum += yield sum; lastSum = temp; } catch (e) { sum = lastSum; } } } const add = adder(0); console.log(add.next()); // 0 console.log(add.next(1)); // 1 console.log(add.next(2)); // 3 console.log(add.throw(new Error('BOO)!'))); // 1 console.log(add.next(4)); // 5
go()
as the next argument. In fact, some caution is needed here. When throw(e)
called, the yield
will work just as if we had written throw e. This means that we must check for the presence of errors every state of our state machine, and blame the program if we cannot handle the error.while
, which we succeeded here. To convert a while
you need to “weave” it into states. So, our case 1 actually involves 2.5 iterations of the while
, because yield
ends in the middle. Finally, you have to add extra code to push exceptions from the caller and back, if there is no try/catch
in the generator to handle this exception.yield
instruction). This will be useful to us in the next section, where we will produce co()
implementation from Corutin..next()
and .throw()
..next()
sleeping generator. She will wait for the generator to do its work, while in the meantime she will be engaged in other asynchronous tasks ... and so on. You listened to a sad story about how the procedure lives in the service of the generator..then()
.co()
is a service procedure that takes on slave labor so that the generator can work only with synchronous values. Already much more logical looks, right? co(function* () { var user = yield fetchJson('/api/user/self'); var interests = yield fetchJson('/api/user/interests?userId=' + self.id); var recommendations = yield Promise.all( interests.map(i => fetchJson('/api/recommendations?topic=' + i))); render(user, interests, recommendations); });
co()
, .co()
, , . co()
.next()
, {done: false, value: [a Promise]}
.next()
,{done: true, value: ...}
, , co()
function deferred(val) { return new Promise((resolve, reject) => resolve(val)); } co(function* asyncAdds(initialValue) { console.log(yield deferred(initialValue + 1)); console.log(yield deferred(initialValue + 2)); console.log(yield deferred(initialValue + 3)); }); function co(generator) { return new Promise((resolve, reject) => { // }); }
co()
, . , . ?co()
. , .throw()
. function deferred(val) { return new Promise((resolve, reject) => resolve(val)); } function deferReject(e) { return new Promise((resolve, reject) => reject(e)); } co(function* asyncAdds() { console.log(yield deferred(1)); try { console.log(yield deferredError(new Error('To fail, or to not fail.'))); } catch (e) { console.log('To not fail!'); } console.log(yield deferred(3)); }); function co(generator) { return new Promise((resolve, reject) => { // }); }
.next()
onResolve()
. onReject()
, .throw()
. try/catch
, , try/catch
.co()
! ! co()
, , , . , ?co()
. - , async/await? — ! , async await
.await
, yield
. await
, async
. async
- .async/await
, , - co()
async
yield
await
, *
, . co(function* () { var user = yield fetchJson('/api/user/self'); var interests = yield fetchJson('/api/user/interests?userId=' + self.id); var recommendations = yield Promise.all( interests.map(i => fetchJson('/api/recommendations?topic=' + i))); render(user, interests, recommendations); });
async function () { var user = await fetchJson('/api/user/self'); var interests = await fetchJson('/api/user/interests?userId=' + self.id); var recommendations = await Promise.all( interests.map(i => fetchJson('/api/recommendations?topic=' + i))); render(user, interests, recommendations); }();
co()
. async , . async
co()
co.wrap()
.co()
( yield
) , , . async
( await
) .co()
, , , async/await
. ? Right.Source: https://habr.com/ru/post/434360/
All Articles