Koa의 비동기 콜백 처리에 대한 심층 분석

高洛峰
풀어 주다: 2016-11-19 15:44:44
원래의
1017명이 탐색했습니다.

1. 콜백 피라미드와 이상적인 솔루션

우리 모두는 JavaScript가 단일 스레드 비동기 비차단 언어라는 것을 알고 있습니다. 비동기 비차단은 확실히 장점 중 하나이지만, 많은 비동기 작업에는 필연적으로 많은 수의 콜백 함수가 포함됩니다. 특히 비동기 작업이 중첩되면 콜백 피라미드 문제가 발생하여 코드 가독성이 매우 떨어집니다. 예를 들면 다음과 같습니다.

var fs = require('fs');

fs.readFile('./file1', function(err, data) {
  console.log(data.toString());
  fs.readFile('./file2', function(err, data) {
    console.log(data.toString());
  })
})
로그인 후 복사

이 예제에서는 두 파일의 내용을 차례로 읽어서 인쇄합니다. file2의 읽기는 file1의 읽기가 완료된 후에 수행되어야 하므로 작업이 수행되어야 합니다. file1을 읽은 후 가져온 콜백 함수에서 실행됩니다. 이는 일반적인 콜백 중첩이며 실제 프로그래밍에서는 더 많은 중첩 수준이 있을 수 있습니다.

우리가 상상하는 더 우아한 작성 방식은 다음과 같이 동기적으로 보이지만 실제로는 비동기적인 작성 방식이어야 합니다.

var data;
data = readFile('./file1');
//下面的代码是第一个readFile执行完毕之后的回调部分
console.log(data.toString());
//下面的代码是第二个readFile的回调
data = readFile('./file2');
console.log(data.toString());
로그인 후 복사

이렇게 작성하면 콜백을 완전히 피할 수 있습니다. 지옥. 실제로 koa를 사용하면 다음과 같은 방식으로 비동기 콜백 함수를 작성할 수 있습니다.

var koa = require('koa');
var app = koa();
var request=require('some module');

app.use(function*() {
  var data = yield request('http://www.baidu.com');
  //以下是异步回调部分
  this.body = data.toString();
})

app.listen(3000);
로그인 후 복사

그렇다면 koa를 그토록 마법처럼 만드는 것은 정확히 무엇일까요?

2. Generator는 비동기 콜백 동기 쓰기 구현을 위해 협력합니다

이전 기사에서 언급했듯이 Generator는 "중단점"과 유사한 효과를 갖는다는 것이 핵심입니다. 항복을 만나면 일시 중지하고 항복 후 함수에 제어권을 넘겨주고 다음에 반환될 때 실행을 계속합니다.

위의 koa 예시에서는 Yield 후에 어떤 객체든 사용할 수 있는 것이 아닙니다! 특정 유형이어야 합니다. co 함수에서는 promise, 썽크 함수 등을 지원할 수 있습니다.

오늘 글에서는 Promise를 예로 들어 Generator와 Promise를 사용하여 비동기 동기화를 구현하는 방법을 분석하고 살펴보겠습니다.

여전히 분석을 위해 파일을 읽는 첫 번째 예를 사용하고 있습니다. 먼저 파일 읽기 기능을 변환하고 이를 Promise 객체로 캡슐화해야 합니다.

var fs = require('fs');

var readFile = function(fileName) {
  return new Promise(function(resolve, reject) {
    fs.readFile(fileName, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    })
  })
}

//下面是readFile使用的示例
var tmp = readFile('./file1');
tmp.then(function(data) {
  console.log(data.toString());
})
로그인 후 복사

Promise 사용과 관련하여 익숙하지 않은 경우 다음 구문을 살펴보세요. es6. (가까운 장래에 es5 구문을 사용하여 기본 기능으로 Promise 객체를 구현하는 방법을 가르치는 기사도 작성할 예정이니 계속 지켜봐 주시기 바랍니다^_^)

간단히 말하면 Promise는 콜백 함수를 구현할 수 있습니다. promise.then(콜백) 형식으로 작성됩니다. 하지만 우리의 목표는 생성기와 협력하여 실제로 매끄럽고 동기화된 쓰기를 달성하는 것입니다. 협력 방법은 다음 코드를 참조하세요.

var fs = require('fs');

var readFile = function(fileName) {
  return new Promise(function(resolve, reject) {
    fs.readFile(fileName, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    })
  })
}

//将读文件的过程放在generator中
var gen = function*() {
  var data = yield readFile('./file1');
  console.log(data.toString());
  data = yield readFile('./file2');
  console.log(data.toString());
}

//手动执行generator
var g = gen();
var another = g.next();
//another.value就是返回的promise对象
another.value.then(function(data) {
  //再次调用g.next从断点处执行generator,并将data作为参数传回
  var another2 = g.next(data);
  another2.value.then(function(data) {
    g.next(data);
  })
})
로그인 후 복사

위 코드에서는 생성기에서 readFile을 생성합니다. 명령문 코드는 항복 후 코드에 작성됩니다. 완전히 동기식이며 기사 시작 부분에서 아이디어를 구현합니다.

yield 후에 우리가 얻는 것은 또 다른 것입니다. value는 promise 객체입니다. then 문을 사용하여 콜백 함수를 정의할 수 있습니다. 함수의 내용은 읽은 데이터를 생성기에 반환하고 계속하는 것입니다. 생성기는 중단점에서 실행됩니다.

기본적으로 이것이 비동기 콜백 동기화의 핵심 원리입니다. 사실 Python에 익숙하신 분이라면 기본적으로 생성기를 사용하여 구현하는 Python에 "코루틴"이라는 개념이 있다는 것을 아실 것입니다(I es6의 생성기가 파이썬에서 차용된 것이 아닌가 하는 의심이 듭니다~)

그러나 우리는 여전히 위의 코드를 수동으로 실행합니다. 따라서 이전 기사와 마찬가지로 생성기 프로세스가 자동으로 실행될 수 있도록 관리하는 실행 기능도 구현해야 합니다!

3. 동기화 콜백 함수 자동 실행: 실행 함수 작성

생성기를 수동으로 실행하는 이전 코드 부분을 주의 깊게 관찰하면 이 패턴도 찾을 수 있습니다. 대신 재귀 함수를 직접 작성할 수 있습니다.

var run=function(gen){
  var g;
  if(typeof gen.next==='function'){
    g=gen;
  }else{
    g=gen();
  }

  function next(data){
    var tmp=g.next(data);
    if(tmp.done){
      return ;
    }else{
      tmp.value.then(next);
    }
  }

  next();
}
로그인 후 복사

함수는 생성기를 수신하고 그 안의 비동기 실행이 자동으로 실행되도록 허용합니다. 이 실행 함수를 사용하여 이전 비동기 코드가 자동으로 실행되도록 합니다.

var fs = require('fs');

var run = function(gen) {
  var g;
  if (typeof gen.next === 'function') {
    g = gen;
  } else {
    g = gen();
  }

  function next(data) {
    var tmp = g.next(data);
    if (tmp.done) {
      return;
    } else {
      tmp.value.then(next);
    }
  }

  next();
}

var readFile = function(fileName) {
  return new Promise(function(resolve, reject) {
    fs.readFile(fileName, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    })
  })
}

//将读文件的过程放在generator中
var gen = function*() {
  var data = yield readFile('./file1');
  console.log(data.toString());
  data = yield readFile('./file2');
  console.log(data.toString());
}
//下面只需要将gen放入run当中即可自动执行
run(gen);
로그인 후 복사

위 코드를 실행하면 터미널이 file1과 file2의 내용을 순서대로 인쇄하는 것을 볼 수 있습니다.

여기서 run 함수는 단순성을 위해 promise만 지원하는 반면 실제 co 함수는 썽크 등도 지원한다는 점을 지적해야 합니다.

이렇게 하면 co 함수의 두 가지 주요 기능이 기본적으로 완전히 도입됩니다. 하나는 어니언 모델의 프로세스 제어이고, 다른 하나는 비동기 동기화 코드의 자동 실행입니다. 다음 기사에서는 이 두 함수를 통합하고 자체 co 함수를 작성하는 방법을 안내하겠습니다!

이 기사의 코드는 github(https://github.com/mly-zju/async-js-demo)에서도 찾을 수 있습니다. 여기서 promise_generator.js는 이 기사의 샘플 소스 코드입니다.


관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿