面接官が満足するGeneratorアクチュエータをどう書きますか?


今はasync関数を使ってGeneratorアクチュエータに代わることができますが、Generatorアクチュエータの原理を知る必要があります.
Generatorを知らないなら、ここを見てください.
例はすべてConsoneで実行できる(Googleバージョン76.0.3809.100).最新のブラウザでサポートされているJavaScript特性で作成されており、互換性は考慮されない.
用語の区分
GeneratorはGenerator functionを実行して戻ってくる対象です.
アクチュエータの原理
原理1
Generator next関数の特性があり、next関数が実行されると次のような構造に戻ります.
{ value: 'world', done: false }
または
{ value: undefined, done: true }
再帰的にnext関数を実行できます.もしdoneがtrueであれば、next関数の運転を停止します.
原理二
yield表現自体は戻り値がない、あるいは常にundefinedに戻ります.
nextメソッドは前のyield表現の戻り値としてパラメータを持つことができます.
nextメソッドのパラメータは前のyield表現の戻り値を表していますので、nextメソッドを初めて使用した場合、伝達パラメータは無効です.
function* test() {
  const a = yield "a"
  console.log(a)
  return true
}
const gen = test()
//     next()     yield a  ,next    {value: "a", done: false}
//     next()          ,    
const nextOne = gen.next() 
//      nextOne.value
// test      console.log    'a'
//      next()      {value: true, done: true}
gen.next(nextOne.value)
原理三gen.throw(exception)は、異常を投げ、生成器の実行を再開し、doneおよびvalueの2つの属性を有するオブジェクトを返すことができる.また、この異常は、一般にtry...catchブロックによって捕捉され得る.私達はPromiseのミスを捨てれば、try catchを使ってPromiseのミスをブロックすることができます.
ただし、注意が必要です.gen.throw()は、同期した環境でコード運転を直接終了し、try catchができないことを知ることが特に重要である.しかし、gen.throw()は、非同期Promise環境においては、リターン値があると同時に、generator関数のtry catch yield文であれば、gen.throwからのエラーをキャプチャすることができる.
したがって、すべてのgen.throw()を一つのPromise実行環境に包装してこそ、有効になることを確認する必要があります.
簡単なアクチュエータ
簡単なアクチュエータコードは以下の通りです.
/**
 * generator    
 * @param {Function} func generator   
 * @return {Promise}      Promise   
 */
function generatorExecuter(func) {
  const gen = func()

  function recursion(prevValue) {
    const next = gen.next(prevValue)
    const value = next.value
    const done = next.done
    if (done) {
      return Promise.resolve(value)
    } else {
      return recursion(value)
    }
  }
  return recursion()
}
コードは複雑ではありません.一番簡単なアクチュエータが出ます.もしあなたが一歩ずつ文章を読んできたなら、原理を理解しています.コードもよく分かります.
yieldを考えるタイプはPromiseです.
上のコードが以下のGenerator関数を実行するのは正しくないです.
function* test() {
  const a = yield Promise.resolve('a')
  console.log(a)
  return true
}
generatorExecuter(test)
上記のコードを実行した後、test関数consolie.logはaではなく、Promise {: "a"}を出力します.
私達のコードはこのように処理する必要があります.
/**
 * generator    
 * @param {GeneratorFunction} generatorFunc generator   
 * @return {Promise}      Promise   
 */
function generatorExecuter(generatorFunc) {
  return new Promise((resolve) => {
    const generator = generatorFunc()
    //              next   
    onFullfilled()
    
    /**
     * Promise          
     *         generator.next()
     * @param {Any} value yield        
     */
    function onFullfilled(value) {
      let result
      // next()          generator       ,
      result = generator.next(value)
      next(result)
    }
    
    function next(result) {
      const value = result.value
      const done = result.done

      if (done) {
        return resolve(value)
      } else {
        return Promise.resolve(value).then(onFullfilled)
      }
    }
  })
}
これでgenerantorExecuterを再稼働すれば大丈夫です.
try catchキャプチャについて考えるyieldエラー
ここは上に述べた原理3を使っています.よく分からないなら帰ってみてもいいです.
上のアクチュエータを使ってはtry catchからyieldまでのエラーはできません.
function* test() {
  try {
    const a = yield Promise.reject('error')
  }catch (err) {
    console.log('     :', err)
  }
  return true
}
generatorExecuter(test)
上記のコードを実行すると、ブロック後にUncaught (in promise) errorを出力するのではなく、 : errorにエラーが発生します.
このように変えなければなりません.
/**
 * generator    
 * @param {GeneratorFunction} generatorFunc generator   
 * @return {Promise}      Promise   
 */
function generatorExecuter(generatorFunc) {
  return new Promise((resolve, reject) => {
    const generator = generatorFunc()
    //              next   
    onFullfilled()
    /**
     * Promise          
     *         generator.next()
     * @param {Any} value yield        
     */
    function onFullfilled(value) {
      let result
      // next()          generator       ,
      //     ,      ,   reject
      try {
        // yield           ,        undefined。
        // generator.next          ,            yield        。
        //    generator.next            yield        ,
        //          generator.next    ,        。
        result = generator.next(value)
      } catch (error) {
        return reject(error)
      }
      next(result)
    }
    /**
     * Promise          
     *         generator.throw() ,     try catch    yield xxx    
     * @param {Any} reason     
     */
    function onRejected(reason) {
      let result
      try {
        // gen.throw()             ,         ,
        //      done   value        。
        // gen.throw()                 ,   try catch,        
        //    gen.throw()     Promise            ,
        //      generator     try catch yield         gen.throw      
        result = generator.throw(reason)
      } catch (error) {
        return reject(error)
      }
      // gen.throw()        ,        
      next(result)
    }

    function next(result) {
      const value = result.value
      const done = result.done

      if (done) {
        return resolve(value)
      } else {
        return Promise.resolve(value).then(onFullfilled, onRejected)
      }
    }
  })
}
yieldの他のタイプを考える
yieldの他のタイプを考慮して、例えばgenerator関数はこれらのタイプをPromiseに適合させることができます.
上のコードが以下のGenerator関数を実行するのは正しくないです.
function* aFun() {
  return 'a'
}

function* test() {
  const a = yield aFun
  console.log(a)
  return true
}
generatorExecuter(test)
上のコードを実行した後、test関数consolie.logはaではなく、下の文字列を出力します.
ƒ* aFun() {
  return 'a'
}
私達のコードはこのように処理する必要があります.
/**
 * generator    
 * @param {GeneratorFunction | Generator} generatorFunc Generator      Generator
 * @return {Promise}      Promise   
 */
function generatorExecuter(generatorFunc) {
  if (!isGernerator(generatorFunc) && !isGerneratorFunction(generatorFunc)) {
    throw new TypeError(
      'Expected the generatorFunc to be a GeneratorFunction or a Generator.'
    )
  }

  let generator = generatorFunc
  if (isGerneratorFunction(generatorFunc)) {
    generator = generatorFunc()
  }

  return new Promise((resolve, reject) => {
    //              next   
    onFullfilled()
    /**
     * Promise          
     *         generator.next()
     * @param {Any} value yield        
     */
    function onFullfilled(value) {
      let result
      // next()          generator       ,
      //     ,      ,   reject
      try {
        // yield           ,        undefined。
        // generator.next          ,            yield        。
        //    generator.next            yield        ,
        //          generator.next    ,        。
        result = generator.next(value)
      } catch (error) {
        return reject(error)
      }
      next(result)
    }
    /**
     * Promise          
     *         generator.throw() ,     try catch    yield xxx    
     * @param {Any} reason     
     */
    function onRejected(reason) {
      let result
      try {
        // gen.throw()             ,         ,
        //      done   value        。
        // gen.throw()                 ,   try catch,        
        //    gen.throw()     Promise            ,
        //      generator     try catch yield         gen.throw      
        result = generator.throw(reason)
      } catch (error) {
        return reject(error)
      }
      // gen.throw()        ,        
      next(result)
    }

    function next(result) {
      const value = toPromise(result.value)
      const done = result.done

      if (done) {
        return resolve(value)
      } else {
        return value.then(onFullfilled, onRejected)
      }
    }
  })
}

/**
 *    yield      ,  generator   
 *            Promise       
 */
function toPromise(value) {
  if (isGerneratorFunction(value) || isGernerator(value)) {
    // generatorExecuter    Promise
    return generatorExecuter(value)
  } else {
    //    、  、  、Promise      Promise
    return Promise.resolve(value)
  }
}

/**
 *     generator   
 */
function isGerneratorFunction(target) {
  if (
    Object.prototype.toString.apply(target) === '[object GeneratorFunction]'
  ) {
    return true
  } else {
    return false
  }
}

/**
 *     generator
 */
function isGernerator(target) {
  if (Object.prototype.toString.apply(target) === '[object Generator]') {
    return true
  } else {
    return false
  }
}
これでgenerantorExecuterを再稼働すれば大丈夫です.
最終版
Generatorアクチュエータは思ったほど難しくないです.時間をかけて十分食べられます.
/**
 * generator    
 * @param {GeneratorFunction | Generator} generatorFunc Generator      Generator
 * @return {Promise}      Promise   
 */
function generatorExecuter(generatorFunc) {
  if (!isGernerator(generatorFunc) && !isGerneratorFunction(generatorFunc)) {
    throw new TypeError(
      'Expected the generatorFunc to be a GeneratorFunction or a Generator.'
    )
  }

  let generator = generatorFunc
  if (isGerneratorFunction(generatorFunc)) {
    generator = generatorFunc()
  }

  return new Promise((resolve, reject) => {
    //              next   
    onFullfilled()
    /**
     * Promise          
     *         generator.next()
     * @param {Any} value yield        
     */
    function onFullfilled(value) {
      let result
      // next()          generator       ,
      //     ,      ,   reject
      try {
        // yield           ,        undefined。
        // generator.next          ,            yield        。
        //    generator.next            yield        ,
        //          generator.next    ,        。
        result = generator.next(value)
      } catch (error) {
        return reject(error)
      }
      next(result)
    }
    /**
     * Promise          
     *         generator.throw() ,     try catch    yield xxx    
     * @param {Any} reason     
     */
    function onRejected(reason) {
      let result
      try {
        // gen.throw()             ,         ,
        //      done   value        。
        // gen.throw()                 ,   try catch,        
        //    gen.throw()     Promise            ,
        //      generator     try catch yield         gen.throw      
        result = generator.throw(reason)
      } catch (error) {
        return reject(error)
      }
      // gen.throw()        ,        
      next(result)
    }

    function next(result) {
      const value = toPromise(result.value)
      const done = result.done

      if (done) {
        return resolve(value)
      } else {
        return value.then(onFullfilled, onRejected)
      }
    }
  })
}

/**
 *    yield      ,  generator   
 *            Promise       
 */
function toPromise(value) {
  if (isGerneratorFunction(value) || isGernerator(value)) {
    // generatorExecuter    Promise
    return generatorExecuter(value)
  } else {
    //    、  、  、Promise      Promise
    return Promise.resolve(value)
  }
}

/**
 *     generator   
 */
function isGerneratorFunction(target) {
  if (
    Object.prototype.toString.apply(target) === '[object GeneratorFunction]'
  ) {
    return true
  } else {
    return false
  }
}

/**
 *     generator
 */
function isGernerator(target) {
  if (Object.prototype.toString.apply(target) === '[object Generator]') {
    return true
  } else {
    return false
  }
}
運行例は以下の通りです.直接にGoogleのコンサートで実行すればいいです.
//   
function* one() {
  return 'one'
}

function* two() {
  return yield 'two'
}

function* three() {
  return Promise.resolve('three')
}

function* four() {
  return yield Promise.resolve('four')
}

function* five() {
  const a = yield new Promise(resolve => {
    setTimeout(() => {
      resolve('waiting five over')
    }, 1000)
  })
  console.log(a)
  return 'five'
}

function* err() {
  const a = 2
  a = 3
  return a
}

function* all() {
  const a = yield one()
  console.log(a)
  const b = yield two()
  console.log(b)
  const c = yield three()
  console.log(c)
  const d = yield four()
  console.log(d)
  const e = yield five()
  console.log(e)
  try {
    yield err()
  } catch (err) {
    console.log('     ', err)
  }
  return 'over'
}

generatorExecuter(all).then(value => {
  console.log(value)
})

//   
// generatorExecuter(all()).then(value => {
//   console.log(value)
// })