I have the following program - I use genny.js to handle the asynchronous flow control - I have tried the same with suspend.js - similar error.
I am using the Stripe nodejs API.
My iterator function seems to be called twice - which is causing an error - and I don't understand why it gets called twice. It must be a simple mind trick that I am not seeing.
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')
})
The console output is:
> 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
Now, if I replace fetchList with the following function it works fine:
fetchList = genny.fn(function* (done) {
console.log('before doTimeout')
console.log('1sec break ...')
yield setTimeout(done(), 1000);
console.log('after doTimeout')
return []
})
The console output is:
> node --harmony genny.js
before run
Before fetchCharges
before doTimeout
1sec break ...
after doTimeout
After fetchCharges - found 0
after run
To further illustrate the fact that the itertor's next() method gets called twice - I have another (non-working) version of the program.
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')
})
And it's console output is here:
> node --harmony genny.js
before run
Before fetchCharges
before fetchList
after fetchList
callback
callback
It's weird - and I don't get it. Can somebody cleverer than me please explain.
UPDATE
I have changed the code to call the stripe methods without a callback or the iterator resume function. And now it works. BUT - curiously - look at the console at the "results". I don't get why. So now it doesn't call the fetchList iterator's next() function "a second time" - but I don't see where it even gets called once!?
var results = yield stripe.charges.list()
Here's the updated complete program.
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')
})
This returns
> node --harmony genny.js
Before run {
fetchCharges {
fetchList {
} fetchList
} fetchCharges - found 10
} after run
results
The problem you are experiencing stems from a mix of two approaches for asynchrony.
The stripe API docs mentions
Every resource method accepts an optional callback as the last argument.
Additionally, every resource method returns a promise.
However, both genny and suspend do
work seamlessly with Node callback conventions and promises
And here lies your mistake: in the line
var results = yield stripe.charges.list({}, done())
you do implicitly use both at the same time. The done()
does create a callback which is passed to stripe, but that call also produces a promise which is yielded, and genny/suspend register another callback on it. This leads to the Error: callback already called
you are observing.
You may choose how you want to fix the problem:
don't yield the promise
var results = yield void stripe.charges.list({}, done())
// ^^^^
don't pass a callback
var results = yield stripe.charges.list({})
(I'd recommend the latter)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With