Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 1 addition & 60 deletions 1-js/11-async/06-promisify/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@

기능을 구현 하다 보면 콜백보다는 프라미스가 더 편리하기 때문에 콜백 기반 함수와 라이브러리를 프라미스를 반환하는 함수로 바꾸는 게 좋은 경우가 종종 생길 겁니다.

<<<<<<< HEAD
<info:callbacks> 챕터에서 사용했던 `loadScript(src, callback)` 예시를 사용해 프라미스화에 대해 좀 더 자세히 알아봅시다.
=======
For better understanding, let's see an example.

For instance, we have `loadScript(src, callback)` from the chapter <info:callbacks>.
>>>>>>> upstream/master

```js run
function loadScript(src, callback) {
Expand All @@ -27,19 +21,7 @@ function loadScript(src, callback) {
// loadScript('path/script.js', (err, script) => {...})
```

<<<<<<< HEAD
`loadScript(src, callback)`를 이제 프라미스화해봅시다. 새로운 함수 `loadScriptPromise(src)`는 `loadScript`와 동일하게 동작하지만 `callback`을 제외한 `src`만 인수로 받아야 하고, 프라미스를 반환해야 합니다.
=======
The function loads a script with the given `src`, and then calls `callback(err)` in case of an error, or `callback(null, script)` in case of successful loading. That's a widespread agreement for using callbacks, we saw it before.
>>>>>>> upstream/master

Let's promisify it.

We'll make a new function `loadScriptPromise(src)`, that does the same (loads the script), but returns a promise instead of using callbacks.

In other words, we pass it only `src` (no `callback`) and get a promise in return, that resolves with `script` when the load is successful, and rejects with the error otherwise.

Here it is:
```js
let loadScriptPromise = function(src) {
return new Promise((resolve, reject) => {
Expand All @@ -54,10 +36,9 @@ let loadScriptPromise = function(src) {
// loadScriptPromise('path/script.js').then(...)
```

<<<<<<< HEAD
새롭게 구현한 `loadScriptPromise`는 프라미스 기반 코드와 잘 융화됩니다.

예시에서 볼 수 있듯이, `loadScriptPromise`는 기존 함수 `loadScript`에 모든 일을 위임합니다. `loadScript`의 콜백은 스크립트 로딩 상태에 따라 `이행` 혹은 `거부`상태의 프라미스를 반환합니다.
예시에서 볼 수 있듯이, `loadScriptPromise`는 기존 함수 `loadScript`에 모든 일을 위임합니다. `loadScript`의 콜백은 스크립트 로딩 상태에 따라 `이행` 혹은 `거부`상태의 프라미스를 반환합니다.

그런데 실무에선 함수 하나가 아닌 여러 개의 함수를 프라미스화 해야 할 겁니다. 헬퍼 함수를 만드는 게 좋을 것 같네요. 프라미스화를 적용 할 함수 `f`를 받고 래퍼 함수를 반환하는 함수 `promisify(f)`를 만들어봅시다.

Expand All @@ -68,21 +49,6 @@ function promisify(f) {
return function (...args) { // 래퍼 함수를 반환함
return new Promise((resolve, reject) => {
function callback(err, result) { // f에 사용할 커스텀 콜백
=======
As we can see, the new function is a wrapper around the original `loadScript` function. It calls it providing its own callback that translates to promise `resolve/reject`.

Now `loadScriptPromise` fits well in promise-based code. If we like promises more than callbacks (and soon we'll see more reasons for that), then we will use it instead.

In practice we may need to promisify more than one function, so it makes sense to use a helper.

We'll call it `promisify(f)`: it accepts a to-promisify function `f` and returns a wrapper function.

```js
function promisify(f) {
return function (...args) { // return a wrapper-function (*)
return new Promise((resolve, reject) => {
function callback(err, result) { // our custom callback for f (**)
>>>>>>> upstream/master
if (err) {
reject(err);
} else {
Expand All @@ -102,26 +68,11 @@ let loadScriptPromise = promisify(loadScript);
loadScriptPromise(...).then(...);
```

<<<<<<< HEAD
위 예시는 프라미스화 할 함수가 인수가 두 개(`(err, result)`)인 콜백을 받을 것이라 가정하고 작성되었습니다. 십중팔구 이런 상황일 것이고, 커스텀 콜백은 이 상황에 딱 들어맞습니다. `promisify`가 잘 동작하는 것은 말할 것도 없겠죠.
=======
The code may look a bit complex, but it's essentially the same that we wrote above, while promisifying `loadScript` function.

A call to `promisify(f)` returns a wrapper around `f` `(*)`. That wrapper returns a promise and forwards the call to the original `f`, tracking the result in the custom callback `(**)`.

Here, `promisify` assumes that the original function expects a callback with exactly two arguments `(err, result)`. That's what we encounter most often. Then our custom callback is in exactly the right format, and `promisify` works great for such a case.
>>>>>>> upstream/master

그런데 함수 `f`가 두 개를 초과하는 인수를 가진 콜백, `callback(err, res1, res2, ...)`을 받는다면 어떤 일이 발생할까요?

<<<<<<< HEAD
이런 경우를 대비하여 좀 더 진화한 헬퍼 함수, `promisify`를 만들어 봅시다. 새롭게 만든 함수를 `promisify(f, true)`형태로 호출하면, 프라미스 결과는 콜백의 성공 케이스(`results`)를 담은 배열, `[res1, res2, ...]`이 됩니다.
=======
We can improve our helper. Let's make a more advanced version of `promisify`.

- When called as `promisify(f)` it should work similar to the version above.
- When called as `promisify(f, true)`, it should return the promise that resolves with the array of callback results. That's exactly for callbacks with many arguments.
>>>>>>> upstream/master

```js
// 콜백의 성공 결과를 담은 배열을 얻게 해주는 promisify(f, true)
Expand Down Expand Up @@ -149,22 +100,12 @@ f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...);
```

<<<<<<< HEAD
`callback(result)`같이 `err`이 없는 형태나 지금까지 언급하지 않은 형태의 이색적인 콜백도 있을 수 있는데, 이런 경우엔 헬퍼 함수를 사용하지 않고 직접 프라미스화 하면 됩니다.
=======
As you can see it's essentially the same as above, but `resolve` is called with only one or all arguments depending on whether `manyArgs` is truthy.

For more exotic callback formats, like those without `err` at all: `callback(result)`, we can promisify such functions manually without using the helper.
>>>>>>> upstream/master

본 챕터에서 설명한 헬퍼 함수보다 더 유용한 형태의 프라미스화를 도와주는 함수를 제공하는 모듈도 많습니다. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify)가 대표적인 예입니다. Node.js에선 내장 함수 `util.promisify`를 사용해 프라미스화를 할 수 있습니다.

```smart
<<<<<<< HEAD
프라미스화는 곧 배우게 될 `async/await`와 함께 사용하면 더 좋습니다. 다만, 콜백을 완전히 대체하지는 못한다는 사실을 기억해 두시기 바랍니다.
=======
Promisification is a great approach, especially when you use `async/await` (covered later in the chapter <info:async-await>), but not a total replacement for callbacks.
>>>>>>> upstream/master

프라미스는 하나의 결과만 가질 수 있지만, 콜백은 여러 번 호출할 수 있기 때문입니다.

Expand Down
16 changes: 0 additions & 16 deletions 1-js/11-async/07-microtask-queue/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,14 @@ alert("코드 종료"); // 얼럿 창이 가장 먼저 뜹니다.

## 마이크로태스크 큐

<<<<<<< HEAD
비동기 작업을 처리하려면 적절한 관리가 필요합니다. 이를 위해 ECMA에선 `PromiseJobs`라는 내부 큐(internal queue)를 명시합니다. V8 엔진에선 이를 '마이크로태스크 큐(microtask queue)'라고 부르기 때문에 이 용어가 좀 더 선호됩니다.
=======
Asynchronous tasks need proper management. For that, the ECMA standard specifies an internal queue `PromiseJobs`, more often referred to as the "microtask queue" (V8 term).
>>>>>>> upstream/master

[명세서](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues)의 설명을 살펴봅시다.

- 마이크로태스크 큐는 먼저 들어온 작업을 먼저 실행합니다(FIFO, first-in-first-out).
- 실행할 것이 아무것도 남아있지 않을 때만 마이크로태스크 큐에 있는 작업이 실행되기 시작합니다.

<<<<<<< HEAD
요약하자면, 어떤 프라미스가 준비되었을 때 이 프라미스의 `.then/catch/finally` 핸들러가 큐에 들어간다고 생각하시면 됩니다. 이때 핸들러들은 여전히 실행되지 않습니다. 현재 코드에서 자유로운 상태가 되었을 때에서야 자바스크립트 엔진은 큐에서 작업을 꺼내 실행합니다.
=======
Or, to put it more simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue; they are not executed yet. When the JavaScript engine becomes free from the current code, it takes a task from the queue and executes it.
>>>>>>> upstream/master

위 예시에서 '코드 종료'가 먼저 출력되는 이유가 여기에 있습니다.

Expand All @@ -48,11 +40,7 @@ Or, to put it more simply, when a promise is ready, its `.then/catch/finally` ha

여러 개의 `.then/catch/finally`를 사용해 만든 체인의 경우, 각 핸들러는 비동기적으로 실행됩니다. 큐에 들어간 핸들러 각각은 현재 코드가 완료되고, 큐에 적체된 이전 핸들러의 실행이 완료되었을 때 실행됩니다.

<<<<<<< HEAD
**그렇다면 '프라미스 성공!'을 먼저, '코드 종료'를 나중에 출력되게 하려면 어떻게 해야 할까요?** 실행 순서가 중요한 경우엔 이런 요구사항이 충족되도록 코드를 작성해야 합니다.
=======
**What if the order matters for us? How can we make `code finished` appear after `promise done`?**
>>>>>>> upstream/master

방법은 아주 쉽습니다. `.then`을 사용해 큐에 넣으면 됩니다.

Expand Down Expand Up @@ -115,11 +103,7 @@ window.addEventListener('unhandledrejection', event => alert(event.reason));

## 요약

<<<<<<< HEAD
모든 프라미스 동작은 '마이크로태스크 큐'(ES8 용어)라 불리는 내부 '프라미스 잡(promise job)' 큐에 들어가서 처리되기 때문에 프라미스 핸들링은 항상 비동기로 처리됩니다.
=======
Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (V8 term).
>>>>>>> upstream/master

따라서 `.then/catch/finally` 핸들러는 항상 현재 코드가 종료되고 난 후에 호출됩니다.

Expand Down
8 changes: 2 additions & 6 deletions 1-js/11-async/08-async-await/02-rewrite-async-2/solution.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@

<<<<<<< HEAD
속임수랄게 없는 문제입니다. `demoGithubUser`안의 `.catch`를 `try...catch`로 교체하고 필요한 곳에 `async/await`를 추가하면 됩니다.
=======
There are no tricks here. Just replace `.catch` with `try..catch` inside `demoGithubUser` and add `async/await` where needed:
>>>>>>> upstream/master
속임수랄게 없는 문제입니다. `demoGithubUser`안의 `.catch`를 `try..catch`로 교체하고 필요한 곳에 `async/await`를 추가하면 됩니다.

```js run
class HttpError extends Error {
Expand Down Expand Up @@ -50,4 +46,4 @@ async function demoGithubUser() {
}

demoGithubUser();
```
```
4 changes: 0 additions & 4 deletions 1-js/11-async/08-async-await/02-rewrite-async-2/task.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@

# async와 await를 사용해서 '다시 던지기' 예시 재작성하기

<<<<<<< HEAD
<info:promise-chaining> 챕터에서 다뤘던 '다시 던지기(rethrow)' 관련 예시를 기억하실 겁니다. 이 예시를 `.then/catch` 대신 `async/await`를 사용해 다시 작성해 봅시다.
=======
Below you can find the "rethrow" example. Rewrite it using `async/await` instead of `.then/catch`.
>>>>>>> upstream/master

그리고 `demoGithubUser` 안의 반복(recursion)은 반복문(loop)을 사용해 작성하도록 합시다. `async/await`를 사용하면 쉽게 작성할 수 있습니다.

Expand Down
10 changes: 0 additions & 10 deletions 1-js/11-async/08-async-await/03-async-from-regular/task.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@

# async가 아닌 함수에서 async 함수 호출하기

<<<<<<< HEAD
'일반' 함수가 하나 있는데, 여기서 `async` 함수를 어떻게 하면 호출하고, 그 결과를 사용할 수 있을까요?
=======
We have a "regular" function called `f`. How can you call the `async` function `wait()` and use its result inside of `f`?
>>>>>>> upstream/master

```js
async function wait() {
Expand All @@ -15,15 +11,9 @@ async function wait() {
}

function f() {
<<<<<<< HEAD
// ...코드...
// async wait()를 호출하고 그 결과인 10을 얻을 때까지 기다리려면 어떻게 해야 할까요?
// f는 일반 함수이기 때문에 여기선 'await'를 사용할 수 없다는 점에 주의하세요!
=======
// ...what should you write here?
// we need to call async wait() and wait to get 10
// remember, we can't use "await"
>>>>>>> upstream/master
}
```

Expand Down
46 changes: 23 additions & 23 deletions 1-js/11-async/08-async-await/04-promise-all-failure/solution.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,52 @@

The root of the problem is that `Promise.all` immediately rejects when one of its promises rejects, but it do nothing to cancel the other promises.
문제의 원인은 `Promise.all`이 프라미스 중 하나라도 거부되면 즉시 거부되지만, 나머지 프라미스를 취소하지는 않는다는 데 있습니다.

In our case, the second query fails, so `Promise.all` rejects, and the `try...catch` block catches this error.Meanwhile, other promises are *not affected* - they independently continue their execution. In our case, the third query throws an error of its own after a bit of time. And that error is never caught, we can see it in the console.
위 예시에서는 두 번째 쿼리가 실패하므로 `Promise.all`이 거부되고, `try...catch` 블록이 이 에러를 잡습니다. 한편, 다른 프라미스는 *아무 영향도 받지 않습니다*. 각자 독립적으로 계속 실행됩니다. 예시에서는 잠시 후 세 번째 쿼리가 자체적으로 에러를 던집니다. 이 에러는 어디에서도 잡히지 않으므로 콘솔에서 확인할 수 있습니다.

The problem is especially dangerous in server-side environments, such as Node.js, when an uncaught error may cause the process to crash.
이 문제는 Node.js 같은 서버 측 환경에서 특히 위험합니다. 잡히지 않은 에러로 인해 프로세스가 중단될 수 있기 때문입니다.

How to fix it?
어떻게 고칠 수 있을까요?

An ideal solution would be to cancel all unfinished queries when one of them fails. This way we avoid any potential errors.
가장 이상적인 해결책은 쿼리 중 하나가 실패했을 때 아직 끝나지 않은 쿼리를 모두 취소하는 것입니다. 이렇게 하면 잠재적인 에러를 피할 수 있습니다.

However, the bad news is that service calls (such as `database.query`) are often implemented by a 3rd-party library which doesn't support cancellation. Then there's no way to cancel a call.
하지만 안타깝게도 `database.query` 같은 서비스 호출은 취소 기능을 지원하지 않는 서드파티 라이브러리로 구현된 경우가 많습니다. 이런 경우 호출을 취소할 방법이 없습니다.

As an alternative, we can write our own wrapper function around `Promise.all` which adds a custom `then/catch` handler to each promise to track them: results are gathered and, if an error occurs, all subsequent promises are ignored.
대안으로 `Promise.all`을 감싸는 래퍼 함수를 직접 작성할 수 있습니다. 이 함수는 각 프라미스에 커스텀 `then/catch` 핸들러를 붙여 상태를 추적합니다. 결과를 모으다가 에러가 발생하면 그 이후 프라미스는 모두 무시합니다.

```js
function customPromiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let resultsCount = 0;
let hasError = false; // we'll set it to true upon first error
let hasError = false; // 첫 번째 에러가 발생하면 true로 바꿉니다.

promises.forEach((promise, index) => {
promise
.then(result => {
if (hasError) return; // ignore the promise if already errored
if (hasError) return; // 이미 에러가 발생했다면 이 프라미스를 무시합니다.
results[index] = result;
resultsCount++;
if (resultsCount === promises.length) {
resolve(results); // when all results are ready - successs
resolve(results); // 모든 결과가 준비되면 성공입니다.
}
})
.catch(error => {
if (hasError) return; // ignore the promise if already errored
hasError = true; // wops, error!
reject(error); // fail with rejection
if (hasError) return; // 이미 에러가 발생했다면 이 프라미스를 무시합니다.
hasError = true; // 에러가 발생했습니다.
reject(error); // 거부 상태로 실패 처리합니다.
});
});
});
}
```

This approach has an issue of its own - it's often undesirable to `disconnect()` when queries are still in the process.
이 방식에도 문제가 있습니다. 쿼리가 아직 처리 중일 때 `disconnect()`를 호출하는 것은 대개 바람직하지 않습니다.

It may be important that all queries complete, especially if some of them make important updates.
특히 일부 쿼리가 중요한 업데이트를 수행한다면 모든 쿼리가 완료되는 것이 중요할 수 있습니다.

So we should wait until all promises are settled before going further with the execution and eventually disconnecting.
따라서 실행을 계속 진행하고 마지막에 연결을 끊기 전에 모든 프라미스가 처리될 때까지 기다려야 합니다.

Here's another implementation. It behaves similar to `Promise.all` - also resolves with the first error, but waits until all promises are settled.
다른 구현을 살펴봅시다. 이 구현은 `Promise.all`과 비슷하게 동작합니다. 첫 번째 에러와 함께 거부되지만, 모든 프라미스가 처리될 때까지 기다립니다.

```js
function customPromiseAllWait(promises) {
Expand Down Expand Up @@ -80,16 +80,16 @@ function customPromiseAllWait(promises) {
}
```

Now `await customPromiseAllWait(...)` will stall the execution until all queries are processed.
이제 `await customPromiseAllWait(...)`는 모든 쿼리가 처리될 때까지 실행을 멈춥니다.

This is a more reliable approach, as it guarantees a predictable execution flow.
실행 흐름을 예측할 수 있게 보장하므로 더 안정적인 방식입니다.

Lastly, if we'd like to process all errors, we can use either use `Promise.allSettled` or write a wrapper around it to gathers all errors in a single [AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError) object and rejects with it.
마지막으로 모든 에러를 처리하고 싶다면 `Promise.allSettled`를 사용하면 됩니다. 또는 `Promise.allSettled`를 감싸는 래퍼를 작성해 모든 에러를 하나의 [AggregateError](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/AggregateError) 객체에 모아 거부할 수도 있습니다.

```js
// wait for all promises to settle
// return results if no errors
// throw AggregateError with all errors if any
// 모든 프라미스가 처리될 때까지 기다립니다.
// 에러가 없으면 결과를 반환합니다.
// 에러가 하나라도 있으면 모든 에러가 담긴 AggregateError를 던집니다.
function allOrAggregateError(promises) {
return Promise.allSettled(promises).then(results => {
const errors = [];
Expand Down
Loading