0

I'm following along with the Kyle Simpson Rethinking Asynchronous JavaScript video course and am confused about how his thunk pattern is using closures. The code is like this:

function ajax(url, cb) {
  $.ajax({ url: `text/${url}`, type: 'GET', dataType: 'json' })
  .done(data => {
    cb(data)
  })
}

function getFile(file) {
  var text, fn;
  ajax(file,function(response){
    if (fn) {
      fn(response)
    } else {
      text = response
    }
  })
  return function th(cb) {
    if (text) {
      cb(text)
    } else {
      fn = cb
    }
  }
}

var th1 = getFile('1')
var th2 = getFile('2')
var th3 = getFile('3')

th1(function ready(text){
  console.log(text)
  th2(function ready(text){
    console.log(text)
    th3(function ready(text){
      console.log(text)
      th2(function (text) {
        console.log(text)
      })
    })
  })
})

I added the extra call to th2 in the final nested portion. I expected this use of closures to return the value originally printed from th2, stored in the closure variable text in the getFile function, ie not make another network call. Though that is not what happens: execution stops after printing the text in the t3 callback.

Why doesn't this closure return its already retrieved value?

1252748
  • 14,597
  • 32
  • 109
  • 229
  • Those thunks look like poor man's promises. I wonder why he threw away the jQuery promise he already had. – Bergi Aug 10 '17 at 02:03
  • @Bergi he did not use jquery. i threw that in there so i could quickly verify what was happening in the network panel. he originally had a `fake_ajax` method that used a timeout. – 1252748 Aug 10 '17 at 02:04
  • @bergi And he uses these thunks as part of the progression toward talking about promises. – 1252748 Aug 10 '17 at 02:05

1 Answers1

0

I expected this use of closures to return the value originally printed from th2, stored in the closure variable text in the getFile function. Though that is not what happens: execution stops after printing the text in the t3 callback.

The problem with these thunks is that you cannot use them twice (at least, when the first use is asynchronous). The value is never stored in the closure variable text. Why? Because th2 was run the first time before the ajax call succeeded, which ran

if (text) // still empty
  …
else // nope, nothing yet, store for later
 fn = cb

Then later, when ajax calls the callback, it will only run

if (fn) // we got something to call
  fn(response)
…

and not text = response. So when th2 is called a second time later (or, worse, immediately from within the callback), it will again just try to store the cb in the fn variable, but nothing will call that.


A possible workaround would be to do

… // in the thunk closure
ajax(file, function(response) {
  text = response;
  if (fn) {
    fn(response)
  }
})

instead which would make your code work, but still be broken: what if th2 is called multiple times before the asynchronous ajax callback? Then cb overwrites the previous fn, so ultimately we would need to maintain an array of callbacks that all should get called. Well, do that and add chainability for the callbacks, and you've got the most basic Promise implementation already.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I don't follow why `text = response` within the callback doesn't work. _Because th2 was run the first time before the ajax call succeeded, which ran `fn=cb`_ : Wy would `fn = cb` be run the first time? – 1252748 Aug 10 '17 at 02:14
  • @1252748 Because there's no `text` yet, so it stores the function. You are calling the returned `th` function for the first time before the `ajax` succeeds. (Notice that it would work if getting file 2 was faster than getting file 1 - urgh, async is hard) – Bergi Aug 10 '17 at 02:22
  • It is hard but it's clear you've thunk about it an awful lot. – 1252748 Aug 10 '17 at 02:37