Реализация простой логики цепочки запросов с обещаниями - PullRequest
0 голосов
/ 22 ноября 2018

Я пытаюсь реализовать фиктивную эмуляцию следующей логики:

enter image description here

Но я не уверен, полностью ли я понимаю лучшие практики того, каксделать это.Основная задача этой задачи - избежать запуска избыточных обратных вызовов catch блоков.IMO, если 1-й запрос завершился неудачно, весь следующий код должен остановиться.

Я имею в виду: если 1-й запрос был неудачным, то мы не делаем 2-й запрос и не вызываем catch блок обещания 2-го запроса.

В нескольких словах я ищу очень чистое и простое решение, подобное следующему:

firstRequest()
    .then(r => {
        console.log('firstRequest success', r);
        return secondRequest();
    }, e => console.log('firstRequest fail', e))
    .then(r => {
        console.log('secondRequest success', r);
        // Should I return something here? Why?
    }, e => console.log('secondRequest fail', e));

Я написал следующую реализацию.Он работает, как и ожидалось, в случае успешного выполнения обоих запросов и сбоя второго запроса.Но это работает неправильно, если 1-й запрос не выполнен (как вы видите, оба блока catch запускаются).Вы можете поиграть с флагами isFirstSucceed и isSecondSucceed, чтобы проверить это.

var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;

var getUsersId = () => new Promise((res, rej) => {
  console.log('request getUsersId');
  setTimeout(() => {
    if (isFirstSucceed) {
      return res([1,2,3,4,5]);
    } else {
      return rej(new Error());
    }
  }, ms);
});

var getUserById = () => new Promise((res, rej) => {
  console.log('request getUserById');
  setTimeout(() => {
    if (isSecondSucceed) {
      return res({name: 'John'});
    } else {
      return rej(new Error());
    }
  }, ms);
});

getUsersId()
.then(r => {
  console.info('1st request succeed', r);
  return getUserById();
}, e => {
  console.error('1st request failed', e);
  throw e;
})
.then(
  r => console.info('2nd request succeed', r), 
  e => {
    console.error('2nd request failed', e);
    throw e;
});

Я могу переместить then 2-го запроса в then 1-го запроса, но это выглядит ужасно.

var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;

var getUsersId = () => new Promise((res, rej) => {
  console.log('request getUsersId');
  setTimeout(() => {
    if (isFirstSucceed) {
      return res([1,2,3,4,5]);
    } else {
      return rej(new Error());
    }
  }, ms);
});

var getUserById = () => new Promise((res, rej) => {
  console.log('request getUserById');
  setTimeout(() => {
	if (isSecondSucceed) {
      return res({name: 'John'});
    } else {
      return rej(new Error());
    }
  }, ms);
});

getUsersId()
.then(r => {
  console.info('1st request succeed', r);
    getUserById().then(
      r => console.info('2nd request succeed', r), 
      e => {
        console.error('2nd request failed', e);
        throw e;
      });
}, e => {
    console.error('1st request failed', e);
    throw e;
})

Вопросы:

  • Как реализовать описанную логику в соответствии с лучшими практиками всех обещаний?
  • Можно ли избежать throw e в каждом catch блоке?
  • Следует ли мне использовать обещания es6?Или лучше использовать какую-нибудь библиотеку обещаний?
  • Любые другие советы?

Ответы [ 6 ]

0 голосов
/ 22 ноября 2018

https://github.com/xobotyi/await-of

$ npm i --save await-of 

import of from "await-of";

async () => {
    let [res1, err1] = await of(axios.get('some.uri/to/get11'));
    let [res2, err2] = await of(axios.get('some.uri/to/get22'));

    if (err1) {
       console.log('err1', err1)
    }
    if (err2) {
       console.log('err2', err2)
    }
    console.log('res1', res1)
    console.log('res2', res2)

};
0 голосов
/ 22 ноября 2018

Вы можете использовать технику печатания утки, чтобы остановить цепочку обещаний с помощью return { then: function() {} };.Я изменил ваш код сразу после этой строки console.error('1st request failed', e);

var ms = 1000;
    var isFirstSucceed = false;
    var isSecondSucceed = true;

    var getUsersId = () => new Promise((res, rej) => {
      console.log('request getUsersId');
      setTimeout(() => {
        if (isFirstSucceed) {
          return res([1,2,3,4,5]);
        } else {
          return rej(new Error());
        }
      }, ms);
    });

    var getUserById = () => new Promise((res, rej) => {
      console.log('request getUserById');
      setTimeout(() => {
        if (isSecondSucceed) {
          return res({name: 'John'});
        } else {
          return rej(new Error());
        }
      }, ms);
    });

    getUsersId()
    .then(r => {
      console.info('1st request succeed', r);
      return getUserById();
    }, e => {
      console.error('1st request failed', e);
      return { then: function() {} };
    })
    .then(
      r => console.info('2nd request succeed', r), 
      e => {
        console.error('2nd request failed', e);
        throw e;
    });
0 голосов
/ 22 ноября 2018

IMO, вы можете использовать async / await ... Тем не менее, с обещаниями, но гораздо чище смотреть.Вот мой пример подхода на приведенной выше логике.

function firstRequest() {
   return new Promise((resolve, reject) =>  {
       // add async function here
       // and resolve("done")/reject("err")
    });
 } 

function secondRequest() {
   return new Promise((resolve, reject) =>  {
       // add async function here
       // and resolve("done")/reject("err")
    });
}

async function startProgram() { 
   try { 
       await firstRequest();
       await secondRequest();
   } catch(err) { 
       console.log(err); 
       goToEndFn();
   }
}

startProgram(); // start the program
0 голосов
/ 22 ноября 2018

вам не нужно обрабатывать ошибку для каждого обещания

вам нужно обрабатывать ошибку только как распространенную ошибку

сделать так:

var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;

var getUsersId = () => new Promise((res, rej) => {
  console.log('request getUsersId');
  setTimeout(() => {
    if (isFirstSucceed) {
      return res([1,2,3,4,5]);
    } else {
      return rej();
    }
  }, ms);
});

var getUserById = () => new Promise((res, rej) => {
  console.log('request getUserById');
  setTimeout(() => {
    if (isSecondSucceed) {
      return res({name: 'John'});
    } else {
      return rej(new Error());
    }
  }, ms);
});

getUsersId()
.then(r => {
  console.info('1st request succeed', r);
  return getUserById();
})
.then(r => {
  console.info('2st request succeed', r);
  return;
})
.catch((e) => {
    console.error('request failed', e);
    throw new Error(e);
})
0 голосов
/ 22 ноября 2018

Ваша блок-схема - это логика, которую вы хотите достичь, но не совсем так, как работают обещания.Проблема в том, что нет способа указать цепочке обещаний просто «закончить» прямо здесь и не вызывать никаких других обработчиков .then() или .catch() позже в цепочке.Если вы получите отказ в цепочке и оставите его отклоненным, он вызовет следующий обработчик .catch() в цепочке.Если вы обрабатываете отклонение локально и не перебрасываете его, тогда он вызовет следующий обработчик .then() в цепочке.Ни один из этих параметров точно не соответствует вашей логической диаграмме.

Итак, вы должны мысленно изменить то, как вы думаете о своей логической диаграмме, чтобы вы могли использовать цепочку обещаний.

Самый простой вариант (чтовероятно, используется для 90% цепочек обещаний) означает просто поместить один обработчик ошибок в конец цепочки.Любая ошибка в любом месте цепочки просто переходит к единственному обработчику .catch() в конце цепочки.К вашему сведению, в большинстве случаев я нахожу код более читабельным с .catch(), чем со вторым аргументом .then(), поэтому я показал его здесь

firstRequest().then(secondRequest).then(r => {
    console.log('both requests successful');
}).catch(err => {
    // An error from either request will show here 
    console.log(err);
});

Когда вы предоставляете уловзаблокировать, и вы не вернете отклоненное обещание или повторно сгенерируете ошибку, тогда инфраструктура обещания думает, что вы «обработали» обещание, поэтому цепочка продолжается в соответствии с решением.Если вы сбросите ошибку, то следующий блок перехвата сработает, и любые промежуточные обработчики .then() будут пропущены.

Вы можете использовать это для локального отлова ошибки, сделать что-то (например, зарегистрировать ее) изатем сбросьте его, чтобы цепочка обещаний была отклонена.

firstRequest().catch(e => {
     console.log('firstRequest fail', e));
     e.logged = true;
     throw e;
}).then(r => {
    console.log('firstRequest success', r);
    return secondRequest();
}).then(r => {
    console.log('secondRequest success', r);
}).catch(e => {
    if (!e.logged) {
        console.log('secondRequest fail', e));
    }
});

Или версия, которая помечает объект ошибки сообщением об отладке, а затем сбрасывает и может регистрировать ошибки только в одном месте:

firstRequest().catch(e => {
     e.debugMsg = 'firstRequest fail';
     throw e;
}).then(r => {
    console.log('firstRequest success', r);
    return secondRequest().catch(e => {
        e.debugMsg = 'secondRequest fail';
        throw e;
    });
}).then(r => {
    console.log('secondRequest success', r);
}).catch(e => {
    console.log(e.debugMsg, e);
});

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

function markErr(debugMsg) {
    return function(e) {
        // mark the error object and rethrow
        e.debugMsg = debugMsg;
        throw e;
    }
}

firstRequest()
  .catch(markErr('firstRequest fail'))
  .then(r => {
    console.log('firstRequest success', r);
    return secondRequest().catch(markErr('secondRequest fail'));
}).then(r => {
    console.log('secondRequest success', r);
}).catch(e => {
    console.log(e.debugMsg, e);
});

Принимаякаждый из ваших вопросов в отдельности:

Как реализовать описанную логику в соответствии с лучшими практиками всех обещаний?

Описано выше.Я бы сказал, что самая простая и лучшая практика - это самый первый блок кода, который я показываю.Если вам нужно убедиться в том, что когда вы доберетесь до финального .catch(), у вас будет уникально идентифицируемая ошибка, чтобы вы знали, какой шаг вызвал ее, то измените отклоненную ошибку в каждой отдельной функции, чтобы она была уникальной, чтобы вы могли определить, из какойодин .catch() блок в конце.Если вы не можете изменить эти функции, вы можете обернуть их оболочкой, которая ловит и помечает их ошибку, или вы можете сделать это в соответствии с решением типа markErr(), которое я показал.В большинстве случаев вам просто нужно знать, что произошла ошибка, а не точный шаг, на котором она произошла, поэтому обычно не требуется для каждого шага в цепочке.

Можно ли избежать броскае в каждом блоке улова?

Это зависит.Если объекты ошибок уже уникальны, то вы можете просто использовать один .catch() в конце.Если объекты ошибок не являются уникальными, но вам необходимо знать, какой именно шаг не удался, то вы должны либо использовать .catch() на каждом шаге, чтобы можно было однозначно пометить ошибку, либо вам нужно изменить каждую функцию в цепочке, чтобыуникальная ошибка.

Должен ли я использовать обещания es6?

Да.Нет лучшего способа, о котором я знаю.

Или лучше использовать библиотеку обещаний?

Мне неизвестны какие-либо функции в библиотеке обещаний, которые могли бы сделатьэто проще.Это на самом деле просто о том, как вы хотите сообщить об ошибках и о том, определяет ли каждый шаг уникальную ошибку или нет.Библиотека обещаний не может сделать это для вас.

Любой другой совет?

Продолжайте узнавать больше о том, как сформировать обещания в решение для каждой отдельной проблемы.

0 голосов
/ 22 ноября 2018

Возможно асинхронное / ожидание?

async function foo() {
    try {
        const firstResult = await firstRequest();
        const secondResult = await secondRequest();
    } catch(e) {
        // e = either first or second error
    }
}

В этом коде ошибка первого запроса передает управление блоку catch, а второй запрос не запускается

Должен ли я использовать обещания es6?

Возможно, да, пока вы не уверены, что ваш код будет использоваться в устаревших средах.Они уже не такие новые и роскошные

...