在 Promise 链中使用 setTimeout

183

我正在学习Promise,第一次请求我获取了一组链接,然后在下一个请求中获取第一个链接的内容。但我想在返回下一个Promise对象之前先延迟一段时间。所以我在上面使用setTimeout。但它会导致以下JSON错误(如果不使用setTimeout()就可以正常工作

SyntaxError: JSON.parse:JSON数据的第1行第1列有意外字符。

我想知道为什么会失败?

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);
});

3
请注意,“return”语句是针对函数的,只会返回给父级函数,并且您无法从异步方法中使用“return”语句返回结果。 - adeneo
2
请注意,有比使用globalObj更好的方法来构建这段代码。这里有更好的方式。 - Bergi
JSON.parse 会在哪里抛出异常?我很难相信一个 then 回调函数中是否有 setTimeout 会影响到前一个 then 回调函数的调用。 - Bergi
这个回答解决了你的问题吗?JavaScript 中类似 sleep() 的函数是什么? - Henke
8个回答

264
为了使 promise 链继续,你不能像以前那样使用 setTimeout() ,因为你没有从.then() 处理程序中返回一个 promise - 你是从 setTimeout() 回调函数中返回的,这对你没有任何帮助。
相反,你可以制作一个简单的延迟函数,像这样:
function delay(t, val) {
    return new Promise(resolve => setTimeout(resolve, t, val));
}

然后像这样使用它:

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");
    });
});

在这里,您从.then()处理程序返回一个Promise,因此它被适当地链接。


您还可以为 Promise 对象添加一个 delay 方法,然后直接在 Promise 上使用 .delay(x) 方法,如下所示:

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);
});


1
resolve函数是then()内部的函数。因此,setTimeout(resolve,t)意味着setTimeout(function(){ return ....},t),不是吗?那么为什么它会起作用呢? - AL-zami
2
@AL-zami - delay() 返回一个 Promise,该 Promise 将在 setTimeout() 之后被解决。 - jfriend00
1
我已经为setTimeout创建了一个promise包装器,以便轻松延迟一个promise。https://github.com/zengfenfei/delay - Kevin
5
@pdem - v是一个可选值,您希望延迟承诺以此来解决并传递到promise链中。 resolve.bind(null,v)代替了function() {resolve(v);}。 两者都可以使用。 - jfriend00
@jfriend00,你能解释一下为什么我们需要resolve.bind(null, v)吗?我试图直接将其更改为resolve(v),但它不起作用。 - LeoShi
显示剩余13条评论

149
.then(() => new Promise((resolve) => setTimeout(resolve, 15000)))

更新:

当我在异步函数中需要睡眠时,我会抛出异常。

await new Promise(resolve => setTimeout(resolve, 1000))

100
答案的ES6简化版本如下:
const delay = t => new Promise(resolve => setTimeout(resolve, t));

然后你可以这样做:

delay(3000).then(() => console.log('Hello'));

1
如果您需要reject选项,例如用于eslint验证,则可以使用以下代码: const delay = ms => new Promise((resolve, reject) => setTimeout(resolve, ms)) - David Thomas
查看如何在 Mocha 测试中使用此功能 - https://stackoverflow.com/a/70348823/1019307 - HankCa

15

如果您处于.then()块内部并且想要执行一个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!!!');

            })

输出结果如下所示

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

愉快地编程!


12
自 node v15 开始,您可以使用 计时器 Promise API
文档中的示例:
import { setTimeout } from 'timers/promises'

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

console.log(res)  // Prints 'result'

4

在Node.js中,您还可以执行以下操作:

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

delay(1000).then(() => console.log('hello'))

我尝试了这个,但在延迟函数中得到了无效的参数数量,期望为0。 - Alex Rindone
我可以确认它在node.js 8、10、12、13中运行正常。不确定你是如何运行你的代码的,但我只能假设util被错误地进行了polyfill。你是否在使用打包工具或其他东西? - Jan

1

对于当前的LTS版本,使用async/await处理超时更为简单。请注意,这是现在推荐使用超时的方式。

然后ables不是推荐的方式。

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')
}

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));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接