131

Here i am trying to wrap my head around promises.Here on first request i fetch a set of links.and on next request i fetch the content of first link.But i want to make a delay before returning next promise object.So i use setTimeout on it.But it gives me the following JSON error (without setTimeout() it works just fine)

SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data

i would like to know why it fails?

let globalObj={};
function getLinks(url){
    return new Promise(function(resolve,reject){

       let http = new XMLHttpRequest();
       http.onreadystatechange = function(){
            if(http.readyState == 4){
              if(http.status == 200){
                resolve(http.response);
              }else{
                reject(new Error());
              }
            }           
       }
       http.open("GET",url,true);
       http.send();
    });
}

getLinks('links.txt').then(function(links){
    let all_links = (JSON.parse(links));
    globalObj=all_links;

    return getLinks(globalObj["one"]+".txt");

}).then(function(topic){


    writeToBody(topic);
    setTimeout(function(){
         return getLinks(globalObj["two"]+".txt"); // without setTimeout it works fine 
         },1000);
});
AL-zami
  • 7,637
  • 11
  • 53
  • 104
  • 2
    Note that `return` is function specific, and returns only to the parent function, and that you can't return from an async method. – adeneo Sep 16 '16 at 19:01
  • 2
    Notice there are [much better ways](http://stackoverflow.com/q/28250680/1048572) to structure this code than to use a `globalObj`. – Bergi Sep 16 '16 at 21:59
  • Where does `JSON.parse` throw? I find it hard to believe that whether there is a `setTimeout` in one `then` callback affects the call in the previous `then` callback. – Bergi Sep 16 '16 at 22:02
  • Does this answer your question? [What is the JavaScript version of sleep()?](https://stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep) – Henke May 27 '21 at 15:41

5 Answers5

213

To keep the promise chain going, you can't use setTimeout() the way you did because you aren't returning a promise from the .then() handler - you're returning it from the setTimeout() callback which does you no good.

Instead, you can make a simple little delay function like this:

function delay(t, v) {
   return new Promise(function(resolve) { 
       setTimeout(resolve.bind(null, v), t)
   });
}

And, then use it like this:

getLinks('links.txt').then(function(links){
    let all_links = (JSON.parse(links));
    globalObj=all_links;

    return getLinks(globalObj["one"]+".txt");

}).then(function(topic){
    writeToBody(topic);
    // return a promise here that will be chained to prior promise
    return delay(1000).then(function() {
        return getLinks(globalObj["two"]+".txt");
    });
});

Here you're returning a promise from the .then() handler and thus it is chained appropriately.


You can also add a delay method to the Promise object and then directly use a .delay(x) method on your promises like this:

function delay(t, v) {
   return new Promise(function(resolve) { 
       setTimeout(resolve.bind(null, v), t)
   });
}

Promise.prototype.delay = function(t) {
    return this.then(function(v) {
        return delay(t, v);
    });
}


Promise.resolve("hello").delay(500).then(function(v) {
    console.log(v);
});

Or, use the Bluebird promise library which already has the .delay() method built-in.

jfriend00
  • 580,699
  • 78
  • 809
  • 825
  • 1
    resolve function is the function inside then()..so setTimeout(resolve,t) means the setTimeout(function(){ return ....},t) isn't it...so why it will work? – AL-zami Sep 16 '16 at 19:22
  • 2
    @AL-zami - `delay()` returns a promise that will be resolved after the `setTimeout()`. – jfriend00 Sep 16 '16 at 19:33
  • I've created a promise wrapper for setTimeout to easily delay a promise. https://github.com/zengfenfei/delay – Kevin Mar 02 '17 at 03:30
  • 4
    @pdem - `v` is an optional value that you would like the delay promise to resolve with and thus pass down the promise chain. `resolve.bind(null, v)` is in place of `function() {resolve(v);}` Either will work. – jfriend00 Mar 13 '18 at 14:41
  • thank you so much... the prototype delay worked but not the function >>> .then statement. the t was undefined. – Christian Matthew Jul 20 '18 at 19:22
  • @ChristianMatthew - The `t` value comes from what you pass `delay(t)` so if you had an undefined `t`, it's probably in how you were calling it. – jfriend00 Jul 20 '18 at 19:51
  • @jfriend00 can you explain why do we need `resolve.bind(null, v)`? I tried to change it directly to `resolve(v)`, it doesn't work. – LeoShi Dec 15 '19 at 08:03
  • @LeoShi - You need the `resolve(v)` to be called LATER when the actual `setTimeout()` timer fires (when it calls its callback). If you just use `resolve(v)`, that calls it immediately BEFORE the timer fires. As an example, you could also do `setTimeout(() => resolve(v), t)`. Also, you have to pass a callback to `setTimeout()` to be able to use the timer. Passing `resolve(v)` executes `resolve(v)` immediately and passes the return value from executing that which is `undefined` so you aren't passing a callback and don't get any notification when the timer fires. – jfriend00 Dec 15 '19 at 16:39
  • Hmmm.... I believe this creates a blocking call though. I am not sure if this is what the OP intended, but if you were to call `delay` and then call something after it, the thing after it wouldn't be called until the `delay` is completed, which is not OK (or shouldn't be). Do you have any ideas how to mitigate this? – Robert Vunabandi Jan 07 '20 at 18:45
  • @RobertVunabandi - It's not blocking (other things can run during the delay), but it intentionally calls the next function AFTER a delay. That is the entire point here. There's no point in using a function like `delay()` if you don't want to call something LATER. This answer shows one how to insert a purposeful delay into a promise chain. That's what this does. – jfriend00 Jan 07 '20 at 18:57
99
.then(() => new Promise((resolve) => setTimeout(resolve, 15000)))

UPDATE:

when I need sleep in async function I throw in

await new Promise(resolve => setTimeout(resolve, 1000))
Igor Korsakov
  • 1,101
  • 7
  • 6
70

The shorter ES6 version of the answer:

const delay = t => new Promise(resolve => setTimeout(resolve, t));

And then you can do:

delay(3000).then(() => console.log('Hello'));
Sébastien Rosset
  • 1,102
  • 7
  • 17
  • and if you need the `reject` option, e.g for eslint validation, then `const delay = ms => new Promise((resolve, reject) => setTimeout(resolve, ms))` – David Thomas Mar 10 '20 at 02:18
12

If you are inside a .then() block and you want to execute a settimeout()

            .then(() => {
                console.log('wait for 10 seconds . . . . ');
                return new Promise(function(resolve, reject) { 
                    setTimeout(() => {
                        console.log('10 seconds Timer expired!!!');
                        resolve();
                    }, 10000)
                });
            })
            .then(() => {
                console.log('promise resolved!!!');

            })

output will as shown below

wait for 10 seconds . . . .
10 seconds Timer expired!!!
promise resolved!!!

Happy Coding!

AnoopGoudar
  • 826
  • 9
  • 18
2

In node.js you can also do the following:

const { promisify } = require('util')
const delay = promisify(setTimeout)

delay(1000).then(() => console.log('hello'))
Jan
  • 7,801
  • 3
  • 35
  • 57
  • I tried this and got invalid number of arguments, expected 0 within the delay function. – Alex Rindone Apr 20 '20 at 20:53
  • I can confirm it works in node.js 8, 10, 12, 13. Not sure how you're running your code but I can only assume `util` is being polyfilled incorrectly. Are you using a bundler or something? – Jan Apr 21 '20 at 15:00