3

I'm trying to learn coding in Javascript and came across this question and have a few questions. What does results.push.bind(results) do in this question? Please see question below:

Suppose getData is a function that takes a query object and returns a promise for the result of the query. Suppose also that someArrayOfQueries is an array of query objects. Explain what would be printed by the following code and why:

function runMultipleQueries(queries) {  
    var results  = [];  
    queries.forEach(doQuery);  
    return results;  
    function doQuery(query) {    
        getData(query)      
            .then(results.push.bind(results));  
    }
}
function log(value) { 
    console.log(value);
}

runMultipleQueries(someArrayOfQueries).forEach(log);
  • 4
    Welcome to Stack Overflow! Please take the [tour], look around, and read through the [help], in particular [*How do I ask a good question?*](/help/how-to-ask) Assignments aren't usually arbitrary; your instructor, tutorial, or course will have covered the necessary topics to make it possible for you to do this. **Review your course materials, class notes, etc., and try to do the work**. *If* you run into a *specific* problem, research it thoroughly, [search thoroughly here](/help/searching), and if you're still stuck post your code and a description of the problem. People will be glad to help. – T.J. Crowder Oct 18 '17 at 17:02
  • 4
    Possible duplicate of https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work, although really what's required here is just research. – T.J. Crowder Oct 18 '17 at 17:02
  • `.bind` is a method that allows you to attach a context to a method. That probably sounds like gibberish to a newcomer. In basic terminology when you have an object with a method you can call that method on any other object as long as you specify using `.bind` or `.apply`, etc. It's an important concept when working within JavaScript, especially with intermediate topics such as currying/closures/etc. so it's worth looking at. I would suggest searching StackOverflow, the MDN (mozilla developer network), and other resources to more thoroughly research before posting here though. – zfrisch Oct 18 '17 at 17:07
  • 1
    Nothing would get printed at all (except maybe for the error message that an array (`results`) doesn't have a `.then()` method. Notice that even [the `push`ing of results won't work](https://stackoverflow.com/q/23667086/1048572) - promises are asynchronous! The proper implementation is `function runMultipleQueries(queries) { return Promise.all(queries.map(getData)); }` – Bergi Oct 18 '17 at 18:07

3 Answers3

2

In Javascript, unlike in other Object Oriented languages, the variable this is quite complicated and can be changed many times.

When working with arrays, the function push takes the array it is called "on", and puts a new element to the end of it. In this case, the push function knows what array it is working on by reading the this variable.

Imagine this example code:

var myDataStructure = {};
myDataStructure.value = 0;
function addOneToValue() {
  this.value += 1;
}
myDataStructure.addOne = addOneToValue;
myDataStructure.addOne(); // here - the variable `this` == myDataStructure

However, if you call the function addOneToValue with the code addOneToValue(), the value of this is not in fact myDataStructure. It is undefined, or a global object like window.*

To manually force addOneToValue to always use myDataStructure as the this value, you can do the following:

addOneToValue = addOneToValue.bind(myDataStructure);

Now you can safely call addOneToValue(), since the this value was bound to myDataStructure.

In your code, runMultipleQuery is passing the function to another handler, who will not call the function like results.push. That means when the function is called, push will again have an uncertain this value. However, by calling bind, we can ensure that runMultipleQuery calls the push function and forces this to be the results variable.

boxmein
  • 844
  • 7
  • 17
  • thank you for the detailed example I now understand what .bind() does! – user8796981 Oct 18 '17 at 19:18
  • No problem! If I answered your question, press the green checkmark to mark the correct answer. When you spend more time on StackOverflow, you will be able to upvote good answers and downvote bad ones as well. – boxmein Oct 19 '17 at 12:12
2

What results.push.bind(results) does is it forces the method call of push to use the scope of results.

TLDR; results is an Array. results.push() is a call to Array#push, but by passing the function results.push directly to the Promise Promise#then it will be called within a different scope.

That is, the keyword this will reference a different object.

By using bind (e.g. - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind), the scope will be "bound" to a specific Object.

The reason for this is because JavaScript is a prototype language, rather than an object-oriented language.

Henry Tseng
  • 2,673
  • 1
  • 15
  • 16
1

What does .push.bind do?

Returns the .length of the array, when called

function runMultipleQueries(queries) {
  var results = [];

  return Promise.all(queries.map(doQuery));

  function doQuery(query) {
    return Promise.resolve(query)
      .then(results.push.bind(results))
  }
  
}

function log(value) {
  console.log(value);
}

runMultipleQueries([10, 20, 30]).then(log);

Note, that as astutely observed and alerted to by @Bergi, the pattern .then(results.push.bind(results)) actually returns the .length of the array.

One solution which still includes the use of the pattern is to chain a second .then() to get the .length returned by previous .then() and return the element of the array by subtracting 1 from the value and using bracket notation

function runMultipleQueries(queries) {
  var results = [];

  return Promise.all(queries.map(doQuery));

  function doQuery(query) {
    return Promise.resolve(query)
      .then(results.push.bind(results))
      .then(len => results[len - 1]);
  }
  
}

function log(value) {
  console.log(value);
}

runMultipleQueries([10, 20, 30]).then(log);

though creating results array is not necessary when using Promise.all(), which returns an array of values


.forEach() alone will not await the previous result of getData() call, results will probably be an empty array at runMultipleQueries(someArrayOfQueries).forEach(log);

Use Promise.all() and .map() instead of .forEach(), return the Promise from getData() call

function runMultipleQueries(queries) {

  return Promise.all(queries.map(doQuery));

  function doQuery(query) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        resolve(query)
      }, Math.floor(Math.random() * 1000))
    })
  }
  
}

function log(value) {
  console.log(value);
}

runMultipleQueries([1,2,3]).then(log);
guest271314
  • 1
  • 10
  • 82
  • 156
  • Can you explain why the first snippet logs `[1, 2, 3]`? I suspect it does not do what you expected, or what the OP expected. – Bergi Oct 18 '17 at 18:09
  • @Bergi _"Can you explain why the first snippet logs `[1, 2, 3]`?"_ Not sure what you mean? What did you conclude OP is expecting? – guest271314 Oct 18 '17 at 18:10
  • I concluded that he expected the results of the `getData` promises to be passed to the final `log`. That's not what's happening here, and I suspect you didn't notice. That's why I'm asking whether you can explain where those numbers come from. – Bergi Oct 18 '17 at 18:13
  • @Bergi Are you referring to the fact that `Promise.all()` returns a new array? And that `results` array is not necessary? Or that `results` array is actually not returned from the function? – guest271314 Oct 18 '17 at 18:14
  • 1
    No, I'm referring to the origin of those numbers (`1`, `2` and `3`). Please walk me through. Or try to pass other numbers and see whether you can explain the result – Bergi Oct 18 '17 at 18:15
  • @Bergi Good catch. `.push()` returns `.length` of the array which is the value returned from `.then()`, correct? – guest271314 Oct 18 '17 at 18:18
  • 1
    Yes, that catch. You might want to adjust that part of your answer. – Bergi Oct 18 '17 at 18:20
  • @Bergi See updated post. Will include your astute observation at Answer; though not certain the description will provide the adequate weight of your alerting the erroneous omission. – guest271314 Oct 18 '17 at 18:24
  • 1
    @Bergi See updated post. If the description is lacking, do advise. – guest271314 Oct 18 '17 at 18:28
  • @gues271314 I now understand why it wouldn't return an results because of async and sync. But why does it return the length of the array? I thought array.push(array) would return the values of the array not the length? – user8796981 Oct 18 '17 at 19:15
  • @user8796981 `var arr = []; console.log(arr.push())`, _"3. Let len be ToLength(Get(O, "length"))."_, _"11. Return len."_ [22.1.3.17 Array.prototype.push ( ...items )](http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.push) – guest271314 Oct 18 '17 at 19:16