использование setTimeout в цепочке обещаний

115

Здесь я пытаюсь сосредоточиться на обещаниях. Здесь по первому запросу я получаю набор ссылок, а по следующему запросу я получаю содержимое первой ссылки. Но я хочу сделать задержку перед возвратом следующего объекта обещания. Поэтому я использую setTimeout, но это дает мне следующую ошибку JSON ( without setTimeout() it works just fine)

SyntaxError: JSON.parse: неожиданный символ в строке 1 столбца 1 данных JSON

я хотел бы знать, почему это не удается?

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-З
источник
1
Обратите внимание, что returnэто зависит от функции и возвращается только в родительскую функцию, и что вы не можете вернуться из асинхронного метода.
adeneo
2
Обратите внимание, что есть гораздо лучшие способы структурировать этот код, чем использование файла globalObj.
Берги
Куда JSON.parseбросает? Мне трудно поверить, что наличие setTimeoutв одном thenобратном вызове влияет на вызов в предыдущем thenобратном вызове .
Берги

Ответы:

191

Чтобы цепочка обещаний продолжалась, вы не можете использовать setTimeout()то, что делали, потому что вы не возвращаете обещание от .then()обработчика - вы возвращаете его из setTimeout()обратного вызова, который вам не помогает.

Вместо этого вы можете сделать простую небольшую функцию задержки, например:

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

А затем используйте это так:

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, а затем напрямую использовать .delay(x)метод в своих обещаниях, например:

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

Или используйте библиотеку обещаний Bluebird, в которой уже есть .delay()встроенный метод.

jfriend00
источник
1
функция resolve - это функция внутри then () .. поэтому setTimeout (resolve, t) означает setTimeout (function () {return ....}, t), не так ли ... так почему это будет работать?
AL-zami
2
@ AL-zami - delay()возвращает обещание, которое будет разрешено после setTimeout().
jfriend00
Я создал оболочку обещания для setTimeout, чтобы легко отложить выполнение обещания. github.com/zengfenfei/delay
Кевин
4
@pdem - vэто необязательное значение, с которым вы хотите, чтобы обещание задержки разрешалось и, таким образом, передавалось по цепочке обещаний. resolve.bind(null, v)вместо function() {resolve(v);} Either будет работать.
jfriend00
Большое вам спасибо ... задержка прототипа сработала, но не оператор функции >>> .then. t не было определено.
Christian Matthew
76
.then(() => new Promise((resolve) => setTimeout(resolve, 15000)))

ОБНОВИТЬ:

когда мне нужно спать в асинхронной функции, я добавляю

await new Promise(resolve => setTimeout(resolve, 1000))
Игорь Корсаков
источник
Не могли бы вы просто спать в такой асинхронной функции? ждать нового обещания (resolve => setTimeout (resolve, 1000));
Anthony Moon Beam Toorie
@AnthonyMoonBeamToorie исправлено, ty
Игорь Корсаков
Добро пожаловать, мой друг 🧐 ура
Энтони Мун Бим Тури
52

Более короткая версия ответа ES6:

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

И тогда вы можете:

delay(3000).then(() => console.log('Hello'));
Себастьен Россе
источник
и если вам нужна rejectопция, например, для проверки eslint, тоconst delay = ms => new Promise((resolve, reject) => setTimeout(resolve, ms))
Дэвид Томас
10

Если вы находитесь внутри блока .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!!!

Удачного кодирования!

AnoopGoudar
источник
-1

В node.js вы также можете делать следующее:

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

delay(1000).then(() => console.log('hello'))
Янв
источник
Я попробовал это и получил недопустимое количество аргументов, ожидалось 0 в функции задержки.
Алекс Риндон
Я могу подтвердить, что он работает в node.js 8, 10, 12, 13. Не знаю, как вы запускаете свой код, но могу только предположить util, что полифиллинг выполняется неправильно. Вы используете упаковщик или что-то в этом роде?
января