2

I am using rethinkDB with node.js. The following request works fine :

function myFn () {
 return co(function *() {
        let query;

        query = yield r.db("my-db")
            .table("app")
            .filter(r.row("id").eq(id))
            .run(conn);

        return query.toArray();
    });
}

I would like to return the result of multiple yield asynchronously, however the following fails :

function myFn () {
 return co(function *() {
        let query, query2;

        query = r.db("my-db")
            .table("app")
            .filter(r.row("id").eq(id))
            .run(conn);


        query2 = r.db("my-db")
            .table("app")
            .filter(...)
            .run(conn);

        return yield {q1 : query, q2 : query2};
    });
}

Then I have to call toArray() on each element, so on the calling function I do :

 // using ramda.js
  var res = R.map((el) => {
            return el.toArray();
        }, yield myFn);

However I get :

{
  "q1": {
    "isFulfilled": false,
    "isRejected": false
  },
  "q2": {
    "isFulfilled": false,
    "isRejected": false
  }
}

Something odd too :

 // this works perfectly
 return q.toArray();

 // this returns the following :
 return {q: q.toArray()};
 "q": {
   "isFulfilled": true,
   "isRejected": false,
   "fulfillmentValue": [  ...  ]
  }

I suspect I am missing something about the way yield works, so how can I return the fulfilled result of multiple yield results ?

nha
  • 17,623
  • 13
  • 87
  • 133
  • 1
    How does it fail exactly? Do you get an error message? Do you get a different result than expected? – nils Nov 20 '15 at 09:56
  • `yield { q1: r.db(" "), q2: r.db(" ")};`. –  Nov 20 '15 at 10:03
  • @nils I updated my question, thanks. – nha Nov 20 '15 at 10:09
  • @torazaburo I'm not sure what you mean by that ? – nha Nov 20 '15 at 10:10
  • I mean, if you return an object, then each property's value will be waited on in parallel, and the yield will "return" when all values have resolved, evaluating to parallel object whose properties' values are the promises' resolved values. In other words, do not yield the individual values, and then yield again an object of the yields; instead, yield once ,with a single object containing all the asynchronous elements. –  Nov 20 '15 at 10:13
  • @torazaburo right, I did that (and updated my question), but still get ```{ "isFulfilled": false, "isRejected": false }``` – nha Nov 20 '15 at 10:23

2 Answers2

2

yield doesn't work with objects that contain promises - it only works with promises itself. Instead of return yield {q1: query, q2: query2}; you'd have to do

return {q1: yield query, q2: yield query2};

However, that is kinda problematic as errors in query2 will not be thrown until query is done. So if you don't just want to sequentially execute them, you will have to use Promise.all to await a collection of promises "in parallel":

var [q1, q2] = yield Promise.all([query, query2]);
return {q1, q2};

(Depending on the promise lib you're using, there might also be a helper function to treat objects as collections, not only arrays)

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • The first approach still returns ```{ "isFulfilled": false, "isRejected": false }```, while the second approach (Promise.all) still returns rethink Cursors (```[ Cursor { _type: 3, _eachCb: [Function], _conn: TcpConnection { host: 'localhost', port: 28015, db: undefined, authKey: '', timeout: 20, ssl: false, outstandingCallbacks: [Object],....``` – nha Nov 20 '15 at 10:49
  • Hm, that doesn't look like `run()` returns a promise. Should you maybe rather `yield` the result of `.toArray()`? I'm not exactly familiar with rethink, but it doesn't look like Cursors are yieldable or thenable. – Bergi Nov 20 '15 at 10:54
  • `run()` returns a Cursor, my understanding is that it can be used with yield as in my first example (also there https://rethinkdb.com/blog/rethinkdbdash-iojs/). – nha Nov 20 '15 at 11:05
  • Hm, their examples only use commands like `create` or `insert`, where `run()` might directly return a promise (for nothing?). I'll assume that `filter().run()` returns a Cursor or a Promise for a Cursor, and `toArray()` then returns a Promise for an Array. Can you try to `yield` or `.all([…])` either `query.toArray()` or `(yield query).toArray()` instead? – Bergi Nov 20 '15 at 11:54
  • I don't actually, not sure if there is something official about this (I am pretty new to rethink). – nha Nov 20 '15 at 12:20
1

So I asked the same question on the reThinkDB google groups :

To quote Ryan Paul :

Your original attempt was actually really close, there were just two things that you need to add:

  • You need to yield the queries individually
  • And you need to convert to array in the query if you don’t want to get cursors

Here’s a working example with the coroutine and yield:

function myFn() {
  return co(function*() {
    var conn = yield r.connect();
    var query1 = yield r.db("test").table("fellowship")
                        .filter({species: "hobbit"})
                        .coerceTo("array").run(conn);

    var query2 = yield r.db("test").table("fellowship")
                        .filter({species: "human"})
                        .coerceTo("array").run(conn);

    conn.close();

    return {q1: query1, q2: query2};
  });
}

It’s also pretty simple to do without the coroutines and yield, and as long as you use a promise library like bluebird. Here’s how I would do it:

function myFn1() {
  return r.connect().then(conn => {
    return bluebird.all([
      r.db("test").table("fellowship")
       .filter({species: "hobbit"})
       .coerceTo("array").run(conn),
      r.db("test").table("fellowship")
       .filter({species: "human"})
       .coerceTo("array").run(conn)
    ]).then(items => ({q1: items[0], q2: items[1]}));
  });
}
nha
  • 17,623
  • 13
  • 87
  • 133
  • 1
    OK, so `.coerceTo("array")` was the missing part in my answer. Thanks for the update! – Bergi Dec 02 '15 at 13:51