2

consider this code

const obj = {
  generate(num) {
    return Math.random()*num;
  },
  add(a,b) {
    return this.generate(a) + this.generate(b)
  }
};

function delay(func, ms) {
  function wrapper() {
    setTimeout(() => func.apply(this, arguments), ms)  // How can I get the return value from the original function?
  }

  return wrapper;
}

// create wrappers
obj.f1000 = delay(obj.add, 1000);
obj.f1500 = delay(obj.add, 1500);

obj.f1000(1,3);
obj.f1500(2,5);

I made a wrapper to delay the method call for add() Is there a way to retrieve the value from the callback inside setTimeout, i.e. the this.generate(a) + this.generate(b) from add()?

Joji
  • 4,703
  • 7
  • 41
  • 86

2 Answers2

1

Just change this line within the wrapper:

setTimeout(() => func.apply(this, arguments), ms) 

to return a Promise (ES6)

return new Promise(resolve => setTimeout(() => resolve(func.apply(this, arguments)), ms));

You can access the delayed result via .then.

Update Per OP's request, edited answer to use async/await insted of then. Since await needs to be called inside a function declared as async, we need to wrap our js code inside a new function (main() below):

const obj = {
  generate(num) {
    return Math.random() * num;
  },
  add(a, b) {
    return this.generate(a) + this.generate(b)
  },

};

function delay(func, ms) {
  function wrapper() {
    // CHANGE THIS LINE
    return new Promise(resolve => setTimeout(() => resolve(func.apply(this, arguments)), ms)); // How can I get the return value from the original function?
  }
  return wrapper;
}

//await can only work in an 'async' function
async function main() {
  obj.f1000 = delay(obj.add, 1000);
  console.time('completed');

  // add await here
  let x = await obj.f1000(1, 3); 
  console.log(x);
  console.timeEnd('completed');
}

main();
chatnoir
  • 2,185
  • 1
  • 15
  • 17
  • Could you use async/await to rewrite this example?? – Joji May 30 '19 at 23:22
  • Yes, but to use `await` instead of `.then` you need to wrap that code inside a function which is declared as `async` first. Updated example code above. – chatnoir May 31 '19 at 05:03
0

Since setTimeout will execute the callback asynchronously, you will need to work with Promises (ES6) or async/await (ES7).

You can polyfill it for ES5.

Example with promises

const obj = {
  generate(num) {
    return Math.random() * num;
  },
  add(a, b) {
    return this.generate(a) + this.generate(b);
  }
};

// create promise
function timedPromise(func, args, ms) {
  return new Promise(resolve => {
    setTimeout(() => {
      // spread operator
      resolve(func.apply(null, args));
    }, ms);
  });
}

// create a function that creates a promise
function delay(func, ms) {
  function wrapper() {
    return timedPromise(func, arguments, ms);
  }

  return wrapper;
}

// obj.add uses this.generate, but 'this' was not bound to obj
obj.f1000 = delay(obj.add.bind(obj), 1000);
obj.f1500 = delay(obj.add.bind(obj), 1500);

console.info('calculating...')
const result1 = obj.f1000(1, 3);
const result2 = obj.f1500(2, 5);

result1.then(result => {
  console.info('f1000', result);
})

result2.then(result => {
  console.info('f1500', result);
})

In your wrapper function, you bound this to the callable function. But since this was not defined in that specific scope, you would have run into some troubles. I bound the obj to the add function while passing it to delay().

Barthy
  • 3,151
  • 1
  • 25
  • 42