【问题标题】:Why is my iterator being advanced again?为什么我的迭代器又进阶了?
【发布时间】:2026-01-17 10:00:02
【问题描述】:

我有以下程序 - 我使用 genny.js 来处理异步流控制 - 我已经尝试使用 suspend.js - 类似的错误。

我正在使用 Stripe nodejs API。

我的迭代器函数似乎被调用了两次——这导致了一个错误——我不明白为什么它被调用了两次。这一定是一个我没有看到的简单的思维技巧。

var genny = require('genny')
genny.longStackSupport = true

var stripe = require("stripe")("sk_live_....")

fetchCharges = genny.fn(function* (d) {
  console.log("Before fetchCharges")
  var charges = yield fetchList(d())
  console.log("After fetchCharges - found ", charges.length)
  return true
})

fetchList = genny.fn(function* (done) {
  console.log("before fetchList")
  var results = yield stripe.charges.list({}, done())
  console.log("after fetchList")
  return results.data
})

genny.run(function* (resume) {
  console.log('before run')
  yield fetchCharges(resume())
  console.log('after run')
})

控制台输出为:

> node --harmony genny.js

before run
Before fetchCharges
before fetchList
after fetchList
After fetchCharges - found  10
after run
/Volumes/dev/ingest/node_modules/genny/index.js:50
        else throw e; 
                   ^
Error: callback already called
    at resume (/Volumes/dev/ingest/node_modules/genny/index.js:154:39)
    at throwAt (/Volumes/dev/ingest/node_modules/genny/index.js:49:30)
    at resume (/Volumes/dev/ingest/node_modules/genny/index.js:153:28)
    at tryProcessPending (/Volumes/dev/ingest/node_modules/genny/index.js:41:28)
    at resume (/Volumes/dev/ingest/node_modules/genny/index.js:164:17)
    at null._onTimeout (/Volumes/dev/ingest/node_modules/stripe/lib/StripeResource.js:87:34)
    at Timer.listOnTimeout (timers.js:110:15)
From generator:
    at /Volumes/dev/ingest/genny.js:22:26

现在,如果我将 fetchList 替换为以下函数,它可以正常工作:

fetchList = genny.fn(function* (done) {
  console.log('before doTimeout')
  console.log('1sec break ...')
  yield setTimeout(done(), 1000);
  console.log('after doTimeout')
  return []
})

控制台输出为:

> node --harmony genny.js

before run
Before fetchCharges
before doTimeout
1sec break ...
after doTimeout
After fetchCharges - found  0
after run

为了进一步说明迭代器的 next() 方法被调用两次这一事实 - 我有另一个(非工作)版本的程序。

var genny = require('genny')
genny.longStackSupport = true

var stripe = require("stripe")("sk_live_...")

fetchCharges = genny.fn(function* (d) {
  console.log("Before fetchCharges")
  var charges = yield fetchList(function(err, cb) {
    console.log("callback")
  })
  console.log("After fetchCharges - found ", charges.length)
  return true
})

fetchList = genny.fn(function* (done) {
  console.log("before fetchList")
  var results = yield stripe.charges.list({}, done())
  console.log("after fetchList")
  return results.data
})

genny.run(function* (resume) {
  console.log('before run')
  yield fetchCharges(resume())
  console.log('after run')
})

它的控制台输出在这里:

> node --harmony genny.js

before run
Before fetchCharges
before fetchList
after fetchList
callback
callback

这很奇怪 - 我不明白。请比我聪明的人解释一下。

更新

我已更改代码以在没有回调的情况下调用条带方法或 迭代器恢复函数。现在它可以工作了。但是 - 奇怪的是 - 在“结果”中查看控制台。我不明白为什么。所以现在它没有“第二次”调用 fetchList 迭代器的 next() 函数——但我看不到它在哪里被调用过一次!?

var results = yield stripe.charges.list()

这是更新后的完整程序。

var genny = require('genny')
genny.longStackSupport = true

var stripe = require("stripe")("sk_live_i6TrEk5lSRM1CmbSZZPsQzKc")

fetchCharges = genny.fn(function* (d) {
  console.log("  fetchCharges {")
  var charges = yield fetchList(d())
  console.log("  } fetchCharges - found ", charges.length)
  return true
})

fetchList = genny.fn(function* (done) {
  console.log("    fetchList {")
  var results = yield stripe.charges.list({}, function(err, results) {
    console.log("results ")
  })
  console.log("    } fetchList")
  return results.data
})

genny.run(function* (resume) {
  console.log('Before run {')
  yield fetchCharges(resume())
  console.log('} after run')
})

返回

> node --harmony genny.js

Before run {
  fetchCharges {
    fetchList {
    } fetchList
  } fetchCharges - found  10
} after run
results 

【问题讨论】:

  • 你确定stripe.charges.list没有多次调用它的回调吗?
  • 检查来自yield stripe.charges.list(...) 的结果是否真的有意义(也就是说,它们是否真的有一个data 数组)。
  • 我已经用代码更新了这个问题,我什至没有为条带调用提供回调或 resume() 函数 - 现在它可以工作了。我无法理解它。我确实用回调函数简单地替换了我作为回调传递给条带调用的 resume() 函数,并且它只被调用了一次(可能是因为在那之后我没有推进迭代器?)
  • 好吧,stripe.charges.list(…) 返回什么?请记录它(在你之前yield它)
  • 如果我将回调传递给条带调用 - 就像你应该做的那样 - 它会按预期从条带返回 json 结果。回调也只被调用一次。

标签: node.js promise generator stripe-payments ecmascript-6


【解决方案1】:

您遇到的问题源于两种异步方法的混合。

stripe API docs 提及

每个资源方法都接受一个可选的回调作为最后一个参数。
此外,每个资源方法都会返回一个 Promise。

但是,gennysuspend 都可以

与节点回调约定承诺无缝工作

这就是你的错误:在行中

var results = yield stripe.charges.list({}, done())

您确实同时使用 bothdone() 确实创建了一个传递给条带的回调,但该调用也产生了一个已产生的承诺,并且 genny/suspend 在其上注册另一个回调。这导致您正在观察的Error: callback already called

您可以选择解决问题的方式:

  • 不要兑现承诺

    var results = yield void stripe.charges.list({}, done())
    //                  ^^^^
    
  • 不要传递回调

    var results = yield stripe.charges.list({})
    

(我推荐后者)

【讨论】:

  • 就是这样!非常感谢您的详细回答。我现在可以继续我的生活了。
最近更新 更多