usando setTimeout na cadeia de promessa

115

Aqui estou eu tentando envolver minha cabeça em torno de promessas. Aqui, na primeira solicitação, eu busco um conjunto de links. E na próxima solicitação, busco o conteúdo do primeiro link. Mas eu quero atrasar antes de retornar o próximo objeto de promessa. setTimeout nele. Mas me dá o seguinte erro JSON ( without setTimeout() it works just fine)

SyntaxError: JSON.parse: caractere inesperado na linha 1 coluna 1 dos dados JSON

gostaria de saber por que falha?

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
fonte
1
Observe que returné específico da função e retorna apenas para a função pai, e que você não pode retornar de um método assíncrono.
adeneo
2
Observe que há maneiras muito melhores de estruturar esse código do que usar um globalObj.
Bergi,
Onde JSON.parsejoga? Acho difícil acreditar que a existência de setTimeoutum thenretorno de chamada afeta a chamada do thenretorno de chamada anterior .
Bergi

Respostas:

191

Para manter a cadeia de promessas em andamento, você não pode usar setTimeout()a maneira como fez porque não está retornando uma promessa do .then()manipulador - você a está retornando do setTimeout()retorno de chamada, o que não adianta nada.

Em vez disso, você pode fazer um pequeno atraso simples funcionar como este:

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

E, em seguida, use-o assim:

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

Aqui, você está devolvendo uma promessa do .then()manipulador e, portanto, ela é devidamente encadeada.


Você também pode adicionar um método de atraso ao objeto Promise e usar diretamente um .delay(x)método em suas promessas como este:

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

Ou use a biblioteca de promessa Bluebird, que já possui o .delay()método integrado.

jfriend00
fonte
1
a função de resolução é a função dentro de then () .. então setTimeout (resolve, t) significa que setTimeout (function () {return ....}, t) não é ... então por que funcionará?
AL-zami
2
@ AL-zami - delay()retorna uma promessa que será resolvida após o setTimeout().
jfriend00
Eu criei um wrapper de promessa para setTimeout para atrasar facilmente uma promessa. github.com/zengfenfei/delay
Kevin
4
@pdem - vé um valor opcional com o qual você deseja que a promessa de atraso resolva e, assim, transmita a cadeia de promessa. resolve.bind(null, v)está no lugar de function() {resolve(v);} Qualquer funcionará.
jfriend00
muito obrigado ... o atraso do protótipo funcionou, mas não a função >>>. instrução então. o t era indefinido.
Christian Matthew
75
.then(() => new Promise((resolve) => setTimeout(resolve, 15000)))

ATUALIZAR:

quando eu preciso dormir na função assíncrona eu coloco

await new Promise(resolve => setTimeout(resolve, 1000))
Igor Korsakov
fonte
Você não poderia simplesmente dormir em uma função assíncrona como esta? aguarda uma nova promessa (resolve => setTimeout (resolve, 1000));
Anthony Moon Beam Toorie
@AnthonyMoonBeamToorie corrigido, ty
Igor Korsakov
52

A versão ES6 mais curta da resposta:

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

E então você pode fazer:

delay(3000).then(() => console.log('Hello'));
Sébastien Rosset
fonte
e se você precisar da rejectopção, por exemplo, para validação eslint, entãoconst delay = ms => new Promise((resolve, reject) => setTimeout(resolve, ms))
David Thomas
10

Se você estiver dentro de um .then () bloco e você quer executar um 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!!!');

            })

a saída será como mostrado abaixo

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

Happy Coding!

AnoopGoudar
fonte
-1

Em node.js, você também pode fazer o seguinte:

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

delay(1000).then(() => console.log('hello'))
Jan
fonte
Eu tentei isso e obtive um número inválido de argumentos, esperado 0 dentro da função de atraso.
Alex Rindone
Posso confirmar que funciona em node.js 8, 10, 12, 13. Não tenho certeza de como você está executando seu código, mas só posso assumir que utilestá sendo polyfilled incorretamente. Você está usando um bundler ou algo assim?
janeiro