자바스크립트와 파이썬의 async generator와 generator를 같이 쓰는 패턴

v8을 읽다가 한 코드를 봤습니다. ES6에서 권장하는 자바스크립트 패턴입니다. 사실 translate 성능을 홍보하려고 넣은 코드인데요. 아래 코드가 바벨로 컴파일하면 무려 2987글자의 코드라고 합니다.

async function* readLines(path) {
  let file = await fileOpen(path);

  try {
    while (!file.EOF) {
      yield await file.readLine();
    }
  } finally {
    await file.close();
  }
}

그와는 별개로 위 코드에서 특이한 점이 yield와 await가 같이 쓰였다는것인데요. 둘이 다른 개념이긴 하지만 같이 쓰이는 패턴을 처음 봐서 조금 파봤습니다. (https://github.com/tc39/proposal-async-iteration위의 사이트에 가면 해당 설명을 볼 수 있습니다.)

파이썬과 다르게 자바스크립트는 비동기에 쓰이는 Async generator와 generator를 같이 쓰는게 가능합니다. 함수 앞에 *은 generator를 나타내며 yield로 iiteraor를 반환합니다. await는 해당 비동기 함수가 실행 된 후까지 기다렸다가 그 해당 함수의 리턴값을 반환받겠다라는 의미입니다. await를 쓰려면 무조건 함수 앞에 async가 붙어야 합니다.

그래서 위 코드에서 yield await file.readLine();는 비동기 작업인 file.readLine()이 끝나길 await로 기다렸다가 그 읽은 값을 yield로 반환한다라고 생각하면 됩니다.

반환 값은 yield가 처리 할 뿐 이 함수에서 await는 return이랑은 관계없이 비동기인 readLine을 반환 받기 위해 쓰인겁니다. (참고로 async에서 return을 하면 해당 값을 프로미스로 감싼 객체가 반환됩니다.) yield가 iterator를 반환하기 때문에 readLines함수를 호출해서 나온 iteraor로 next()를 호출해 파일을 한줄 씩 읽을 수 있습니다.

python

하지만 python에서는 async와 yield는 공존할 수가 없다고 합니다. 같이 쓰려고 하면 아래와 같은 오류가 뜹니다.

'yield' inside async function

사실 안될 이유는 없을 것 같아서 찾아보니 python 3.6에서는 asnyc generator에서도 iterator를 쓸 수 있게 해줍니다. asnyc/await가 나온지 얼마 안되다 보니 나온 것 같아요.

PEP 525: Asynchronous Generators
PEP 492 introduced support for native coroutines and async / await syntax to Python 3.5. A notable limitation of the Python 3.5 implementation is that it was not possible to use await and yield in the same function body. In Python 3.6 this restriction has been lifted, making it possible to define asynchronous generators:

async def ticker(delay, to):
    """Yield numbers from 0 to *to* every *delay* seconds."""
    for i in range(to):
        yield i
        await asyncio.sleep(delay)
The new syntax allows for faster and more concise code.

아니면 async gererator의 aiteranext 매직 매소드를 조금 손봐서 비동기 iterator를 구현 할 수가 있다고 하네요.

comments powered by Disqus