Node. js 리 셋 블랙홀 전 해: Async, Promise, Generator

30414 단어 NodeJS
다음으로 이동:http://zhuanlan.zhihu.com/FrontendMagazine/19750470
우 리 는 항상 이 문 제 를 '블랙홀 반전' 또는 '피라미드 반전' 이 라 고 부른다.
doAsync1(function () {
  doAsync2(function () {
    doAsync3(function () {
      doAsync4(function () {
    })
  })
})

블랙홀 을 되 돌 리 는 것 은 주관적 인 명칭 으로, 마치 너무 많은 코드 를 끼 워 넣 은 것 처럼, 때로는 아무런 문제 도 없다.호출 순 서 를 제어 하기 위해 비동기 코드 가 매우 복잡 해 졌 는데 이것 이 바로 블랙홀 이다.블랙홀 이 얼마나 깊 은 지 가늠 하기에 적합 한 문제 가 있다.
만약 doAsync 2 가 doAsync 1 이전에 발생 한다 면, 당신 은 얼마나 많은 재 구성 고통 을 참아 야 합 니까?
목 표 는 내장 층 수 를 줄 이 는 것 이 아니 라 모듈 화 (테스트 가능) 코드 를 작성 하여 이해 하고 수정 하 는 것 입 니 다.
본 논문 에서 우 리 는 하나의 모듈 을 만들어 서 일련의 도구 와 라 이브 러 리 를 사용 하여 흐름 제어 가 어떻게 작 동 하 는 지 보 여 줘 야 한다.그리고 새로운 Node 가 새로운 잠재력 있 는 해결 방안 을 가 져 왔 으 니 끝까지 알 아 보 겠 습 니 다.
문제.
만약 우리 가 어떤 디 렉 터 리 에서 가장 큰 파일 을 찾 아야 한다 고 가정 한다.
var findLargest = require('./findLargest')
findLargest('./path/to/dir', function (er, filename) {
  if (er) return console.error(er)
  console.log('largest file was:', filename)
})

우 리 는 단계별 로 이 문 제 를 해결한다. 
  • 주어진 폴 더 의 모든 파일 읽 기
  • 파일 마다 stats (상태) 가 져 오기
  • 그 파일 이 가장 큰 지 확인 합 니 다 (여러 파일 이 가장 큰 경우 그 중 하 나 를 선택 하 십시오)
  • 최대 파일 의 파일 이름 을 리 셋 함수
  • 에 전달 합 니 다.
    모든 부분 에 오류 가 발생 하면 리 셋 함 수 를 호출 하여 오 류 를 전달 합 니 다.우 리 는 단지 리 셋 함 수 를 한 번 호출 할 뿐이다.
    내장 코드
    첫 번 째 해결 방안 은 포 함 된 것 으로 무 서운 것 이 없어 보이 고 논 리 는 알 아 볼 수 있다.
    var fs = require('fs')
    var path = require('path')
    
    
    module.exports = function (dir, cb) {
      fs.readdir(dir, function (er, files) { [1]
        if (er) return cb(er)
        var counter = files.length
        var errored = false
        var stats = []
    
    
        files.forEach(function (file, index) {
          fs.stat(path.join(dir,file), function (er, stat) { [2]
            if (errored) return
            if (er) {
              errored = true
              return cb(er)
            }
            stats[index] = stat [3]
    
    
            if (--counter == 0) { [4]
              var largest = stats
                .filter(function (stat) { return stat.isFile() }) [5]
                .reduce(function (prev, next) { [6]
                  if (prev.size > next.size) return prev
                  return next
                })
              cb(null, files[stats.indexOf(largest)]) [7]
            }
          })
        })
      })
    }
    
  • 디 렉 터 리 에 있 는 모든 파일 읽 기
  • 모든 파일 의 stats 를 읽 습 니 다.읽 는 과정 이 병행 되 기 때문에 저 희 는 conter 를 사용 하여 I / O 의 끝 을 추적 합 니 다.동시에 우 리 는 여러 번 의 오류 로 인해 여러 번 호출 함수 (cb)
  • 를 방지 하기 위해 불 변수 errored 를 사용 했다.
  • 파일 마다 stats 를 수집 합 니 다.여기 병렬 배열 (files 에서 stats 까지)
  • 을 설정 하 였 습 니 다.
  • 검사 병행 호출 완료
  • 일반 파일 필터 링 (링크, 디 렉 터 리 등 포함 되 지 않 음)
  • 전체 목록 을 줄 이 고 가장 큰 파일 가 져 오기
  • stat 에 따라 파일 이름 을 가 져 오고 리 셋 함수
  • 를 호출 합 니 다.
    이 해결 방안 은 그런대로 괜 찮 지만, 그것 은 몇 가지 기 교 를 운용 하여 병행 처 리 를 관리 하고, 여러 번 호출 함 수 를 피한다.우 리 는 그 후에 다시 이 문제 들 을 처리 할 것 이다. 우선 이 코드 를 더 작은 모듈 로 나 누 자.
    모듈 화
    코드 를 끼 워 넣 는 방안 은 세 개의 모듈 단위 로 나 눌 수 있다. 
  • 디 렉 터 리 에서 모든 파일 읽 기
  • 이 파일 에서 stats
  • 를 가 져 옵 니 다.
  • stats 와 files 를 처리 하고 가장 큰 파일 가 져 오기
  • 첫 번 째 모듈 은 사실...
    fs.readdir
    우 리 는 그것 을 위해 함 수 를 새로 쓰 지 않 을 것 이다.단, 우 리 는 일련의 파일 경 로 를 정 하고 이 파일 들 의 stat 를 되 돌려 주 는 함 수 를 쓸 수 있 습 니 다.
    function getStats (paths, cb) {
      var counter = paths.length
      var errored = false
      var stats = []
      paths.forEach(function (path, index) {
        fs.stat(path, function (er, stat) {
          if (errored) return
          if (er) {
            errored = true
            return cb(er)
          }
          stats[index] = stat
          if (--counter == 0) cb(null, stats)
        })
      })
    }
    

    다음은 stats 와 files 를 비교 하여 최대 파일 의 파일 이름 을 되 돌려 주 는 처리 함수 가 필요 합 니 다.
    function getLargestFile (files, stats) {
      var largest = stats
        .filter(function (stat) { return stat.isFile() })
        .reduce(function (prev, next) {
          if (prev.size > next.size) return prev
          return next
        })
        return files[stats.indexOf(largest)]
    }
    

    한데 묶 기:
    var fs = require('fs')
    var path = require('path')
    
    
    module.exports = function (dir, cb) {
      fs.readdir(dir, function (er, files) {
        if (er) return cb(er)
        var paths = files.map(function (file) { [1]
          return path.join(dir,file)
        })
    
    
        getStats(paths, function (er, stats) {
          if (er) return cb(er)
          var largestFile = getLargestFile(files, stats)
          cb(null, largestFile)
        })
      })
    }
    

    모듈 화 된 해결 방안 은 코드 재 활용 과 테스트 를 더욱 쉽게 한다.핵심 적 인 export 방법 도 이해 하기 쉽다.그러나, 우 리 는 수 동 으로 병행 하 는 stat 작업 을 관리 합 니 다.흐름 제어 라 이브 러 리 를 사용 해 보 겠 습 니 다. 우리 가 무엇 을 할 수 있 는 지 볼 까요?
    Async async
    모듈 은 노드 의 핵심 정신과 유사 하 게 유행 하고 있다.async 를 사용 하여 코드 를 재 구성 하 는 방법 을 알 아 보 겠 습 니 다.
    var fs = require('fs')
    var async = require('async')
    var path = require('path')
    
    
    module.exports = function (dir, cb) {
      async.waterfall([ [1]
        function (next) {
          fs.readdir(dir, next)
        },
        function (files, next) {
          var paths = 
           files.map(function (file) { return path.join(dir,file) })
          async.map(paths, fs.stat, function (er, stats) { [2]
            next(er, files, stats)
          })
        },
        function (files, stats, next) {
          var largest = stats
            .filter(function (stat) { return stat.isFile() })
            .reduce(function (prev, next) {
            if (prev.size > next.size) return prev
              return next
            })
            next(null, files[stats.indexOf(largest)])
        }
      ], cb) [3]
    }
    
  • async. waterfall 은 폭포 식 흐름 통 제 를 제공한다.모든 조작 에서 발생 하 는 데 이 터 는 다음 함수 에 전달 할 수 있 으 며, next 라 는 반전 함수
  • 를 통 해 전달 할 수 있 습 니 다.
  • async. map 는 일련의 path 에 fs. stat 를 호출 하고 결과 배열 을 반전 함수
  • 에 전달 할 수 있 도록 합 니 다.
  • 마지막 단계 후에 리 셋 함수 cb 를 호출 합 니 다.전체 운행 과정 에서 오류 가 발생 하면 cb 를 호출 합 니 다. 한 번 만 호출 되 지 않 습 니 다
  • async 모듈 은 리 셋 이 한 번 만 실 행 될 것 을 보증 합 니 다.그것 은 동시에 우 리 를 위해 오 류 를 처리 하고 병행 하 는 임 무 를 관리 했다.
    Promise Promise
    오류 처리 와 함수 식 프로 그래 밍 을 제공 합 니 다.
    。우 리 는 어떻게 Promise 를 사용 하여 이 문 제 를 해결 합 니까?Q 를 사용 하 래 요.
    모듈 시험 해 보기 (물론 다른 Promise 라 이브 러 리 도 사용 할 수 있 습 니 다):
    var fs = require('fs')
    var path = require('path')
    var Q = require('q')
    var fs_readdir = Q.denodeify(fs.readdir) [1]
    var fs_stat = Q.denodeify(fs.stat)
    
    
    module.exports = function (dir) {
      return fs_readdir(dir)
        .then(function (files) {
          var promises = files.map(function (file) {
            return fs_stat(path.join(dir,file))
          })
          return Q.all(promises).then(function (stats) { [2]
            return [files, stats] [3]
          })
        })
        .then(function (data) { [4]      var files = data[0]
          var stats = data[1]      var largest = stats
            .filter(function (stat) { return stat.isFile() })
            .reduce(function (prev, next) {
            if (prev.size > next.size) return prev
              return next
            })
          return files[stats.indexOf(largest)]
        })
    }
    
  • Node 커 널 은 promise 화 된 것 이 아니 라 전환 하 는 것
  • Q. all 은 모든 stat 를 동시에 실행 한 결과 배열 은 원래 의 순 서 를 유지 했다
  • .
  • 마지막 으로 files 와 stat 를 next 함수
  • 에 전달 합 니 다.
    이전 예 와 달리 promise 체인 에서 던 진 오 류 는 처 리 됩 니 다.그리고 드 러 난 API 도 Promise 화 된 것 입 니 다.
    var findLargest = require('./findLargest')
    findLargest('./path/to/dir')
      .then(function (er, filename) {
        console.log('largest file was:', filename)
      })
      .catch(console.error)
    

    위 에 이렇게 디자인 되 어 있 음 에 도 불구 하고 promise 화 된 인 터 페 이 스 를 노출 할 필 요 는 없다.많은 promise 라 이브 러 리 도 node 스타일 의 인 터 페 이 스 를 노출 하 는 방법 을 제공 했다.Q 에서 nodeify 를 사용 할 수 있 습 니 다.
    함수 로 실현 하 다.
    Promise 를 깊이 소개 하지 않 겠 습 니 다. 더 알 고 싶다 면 이 글 을 읽 으 세 요.
    Generator
    본 고 는 처음부터 말 했 듯 이 이 분야 에서 신기 술 이 왔 는데 Node 0.11.2 와 이상 버 전에 서 이미 사용 할 수 있 습 니 다.
    Generator!
    Generator 는 자 바스 크 립 트 를 위 한 경량급 협정 이다.이 는 yield 키 워드 를 통 해 함수 가 일시 정지 되 거나 Generator 함 수 를 계속 실행 하 는 것 을 제어 할 수 있 습 니 다. 특별한 문법 function * () 이 있 습 니 다. 이러한 초능력 을 통 해 우 리 는 비동기 작업 을 일시 정지 하거나 계속 수행 할 수 있 습 니 다. promise 나 thunks 와 같은 구 조 를 사용 하여 동기 화 된 코드 를 쓸 수 있 습 니 다.
    Thunk 함 수 는 이러한 함수 입 니 다. 반전 을 되 돌려 자신 을 호출 합 니 다.리 턴 함수 와 전형 적 인 node 리 턴 함수 가 일치 하 는 매개 변 수 를 가지 고 있 습 니 다 (예 를 들 어 error 는 첫 번 째 매개 변수 입 니 다).여 기 를 읽 으 면 더 많이 알 수 있다.
    Generator 를 어떻게 사용 하여 비동기 적 인 통 제 를 하 는 지 예 를 들 어 보 겠 습 니 다. TJ Holowaychuk 의 라 이브 러 리 코 입 니 다.다음은 우리 가 가장 큰 파일 을 찾 는 프로그램 입 니 다.
    var co = require('co')
    var thunkify = require('thunkify')
    var fs = require('fs')
    var path = require('path')
    var readdir = thunkify(fs.readdir) [1]
    var stat = thunkify(fs.stat)
    
    
    module.exports = co(function* (dir) { [2]  var files = yield readdir(dir) [3]
      var stats = yield files.map(function (file) { [4]
        return stat(path.join(dir,file))
      })
      var largest = stats
        .filter(function (stat) { return stat.isFile() })
        .reduce(function (prev, next) {
          if (prev.size > next.size) return prev
          return next
        })
      return files[stats.indexOf(largest)] [5]
    })
    
  • Node 핵심 함수 가 thunk 화 된 것 이 아니 므 로 우리 thunk 지
  • co 는 Generator 함 수 를 받 아들 입 니 다. 이 함수 내부 에서 yield 키 워드 를 사용 하여 어디에서 든 일시 정지 할 수 있 습 니 다
  • Generator 함 수 는 readdir 가 돌아 올 때 까지 계속 실 행 됩 니 다.결과 할당 files 변수
  • co 는 일련의 병렬 작업 배열 도 처리 할 수 있 고 결 과 는 stats 라 는 배열 에 순서대로 저장 할 수 있 습 니 다
  • 최종 결과 복귀
  • 우 리 는 이 Generator 함 수 를 본문 이 처음 시작 한 것 처럼 똑 같은 리 셋 API 로 포장 할 수 있다.Co 는 또 모든 오 류 를 반전 함수 에 되 돌려 줄 수 있다.Generator 에서 try / catch 를 사용 하여 yield 문 구 를 감 쌀 수 있 습 니 다. co 는 이 점 을 이용 합 니 다.
    try {
      var files = yield readdir(dir)
    } catch (er) {
      console.error('something happened whilst reading the directory')
    }
    

    Co 는 배열, 대상, 내장 Generator, Promise 등 도 우아 하 게 지원 할 수 있다.
    다른 Generator 모듈 도 생 겨 났 다.Q 모듈 에서 우아 한 Q. async 를 신 고 했 습 니 다.
    방법, 행 위 는 co 사용 Generator 와 일치 합 니 다.
    총결산
    본 논문 에서 우 리 는 블랙홀 을 되 돌 리 는 여러 가지 다른 방안 을 연구 했다.사실은 프로그램의 절차 통제 다.저 는 개인 적 으로 Generator 의 방안 에 관심 이 많 습 니 다.나 는 koa 와 같은 새로운 틀 이 무엇 을 가 져 올 수 있 는 지 매우 궁금 하 다.
    본 논문 에서 사용 한 코드 예제 와 다른 Generator 예 를 원 하 십 니까?여기 Github repo 가 있어 요. 만족 할 수 있어 요!본 고 는 StrongLoop | Managing Node. js Callback Hell with Promises, Generators and Other Approaches 에서 시작 되 었 습 니 다.

    좋은 웹페이지 즐겨찾기