182

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);
});
Penny Liu
  • 15,447
  • 5
  • 79
  • 98
AL-zami
  • 8,902
  • 15
  • 71
  • 130
  • 3
    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

8 Answers8

260

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, val) {
    return new Promise(resolve => setTimeout(resolve, t, val));
}

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, val) {
    return new Promise(resolve => setTimeout(resolve, t, val));
}

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


Promise.resolve("hello").delay(500).then(function(val) {
    console.log(val);
});
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 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
  • 1
    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
  • 5
    @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
  • `Promise.prototype.delay=function(t) {return this.then((v) => new Promise((r) => setTimeout(()=>r(v),t)))};` – unsynchronized Jun 16 '21 at 09:09
  • also, if you add `Promise.timeout = function (t, v) { return Promise.resolve(v).delay(t);}` lets you do `Promise.timeout(500,"hello").then(console.log.bind(console))` (2nd argument is optional) – unsynchronized Jun 16 '21 at 09:21
  • Beautiful! Worked like a charm. Thank you so much! I returned it from my function, then used async/await when I called my function, and the delay happened as expected. My version: ```function wait(time) { return new Promise((resolve) => { setTimeout(resolve.bind(null), time); }); }``` – user1305454 Oct 15 '22 at 15:38
  • @user1305454 - I updated the `delay()` implementation to be a bit simpler. – jfriend00 Oct 15 '22 at 18:28
  • descriptive variable names would make this answer stronger. – lowcrawler May 08 '23 at 20:28
  • @lowcrawler - Which variable names are you thinking of with that comment? I changed `v` to `val`, but not sure what else you were referring to. – jfriend00 May 08 '23 at 22:05
148
.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,589
  • 1
  • 9
  • 6
100

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,481
  • 1
  • 10
  • 17
  • 1
    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
  • See how to use this in a Mocha test - https://stackoverflow.com/a/70348823/1019307 – HankCa Dec 14 '21 at 12:28
15

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
  • 914
  • 9
  • 18
10

Since node v15, you can use timers promise API

example from the doc:

import { setTimeout } from 'timers/promises'

const res = await setTimeout(100, 'result')

console.log(res)  // Prints 'result'
kigiri
  • 2,952
  • 21
  • 23
3

In node.js you can also do the following:

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

delay(1000).then(() => console.log('hello'))
Jan
  • 8,011
  • 3
  • 38
  • 60
  • 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
1

For the current LTS its easier and we can use async/await to handle timeouts. Please note that this is the recommended way to use timeout nowadays.

Thenables is not the recommended way.

const { promisify } = require('util')
const sleep = promisify(setTimeout)
async function myFunction() {
  await sleep(1e3)
  console.log('This will be seen after 1 sec')
  await sleep(5e3)
  console.log('This will be seen after 5 sec after')
}
shijin
  • 2,998
  • 1
  • 24
  • 30
0
const myStuff = new Promise(function (resolve) {
  console.log("before timeout");
  setTimeout(
    function (x) {
      console.log("inside the timeout");
      resolve(x);
    },
    3000,
    "after timeout"
  );
}).then((response) => console.log(response));