от нуля до героя в 25 испытаниях

1656600610 ot nulya do geroya v 25 ispytaniyah

автор Андреа Кутифарис

1*JZlPOGIMUKYlwvBnmPomEg

Тест стоит тысячи слов… это была картина…?

Я думаю, что лучший способ объяснить обещания JavaScript – это на примерах. Какой хороший, самостоятельный и краткий способ написать пример? Тест!

Для тех, кто никогда не видел тестовый костюм Жасмин, it('...', (done) => {...}) является тестом and dодна – функция, которая должна быть выполнена после завершения асинхронного теста.

Правила здесь таковы:

  • Каждый тест начинается с того, что что-то утверждает на английском. Вы должны понять, почему тестовый код означает, что утверждение теста истинно.
  • Некоторые тесты ожидают. Если тест пройдет, ожидания сбываются.
  • Другие тесты полагаются на обратный вызов done() Быть призванным. Если done() не вызывается, тест не проходит.

Каждый тест находится в этом JSFiddle, поэтому не стесняйтесь играть с ним во время чтения. Особенно если у вас есть сомнения в любом из тестов, измените тестовый код и изучите, что происходит.

Тесты

Начнем с основ Promise:

it('Promise executor is run SYNCHRONOUSLY', () => {  let executorRun = false;  new Promise(function executor() {    executorRun = true;  });  expect(executorRun).toBe(true);});it('you can resolve a promise', (done) => {  new Promise((resolve) => setTimeout(resolve, 1))    .then(done);});it('... or you can reject a promise', (done) => {  new Promise((resolve, reject) => setTimeout(reject, 1))    .then(undefined, done);});it('An error inside the executor, rejects the promise', (done) => {  new Promise(function executor() {    throw 'Error';  }).catch(done);});

Кажется, что когда звонишь resolve() первый then(...) выполняется обратный вызов. Если вы позвоните reject() или выдается ошибка, catch() или второй обратный вызов then(...) запускается.

также, исполнитель обещаний запущен синхронно. Это означает, что обещания – это способ обработки асинхронного кода, а не выполнение задач в асинхронных потоках. Используйте Web Workers, если вы хотите выполнить некоторый код JavaScript вне основного потока.

Рассмотрим подробнее, что это такое then(...) и catch() функции, и что означает «цепочка обещаний»:

// Chaining promises
it('you can chain promise because .then(...) returns a promise'  , (done) => {  fetch('    .then(response => response.json())    .then(json => expect(json.userId).toBe(1))    .then(done);});it('you can use the fail callback of .then(success, fail) to ' +  'handle rejected promises', (done) => {  Promise.reject()    .then(function success() {      throw 'I must not be executed';    }, function fail() {      done();    });});it('... or you can use .catch() to handle rejected promises'  , (done) => {  Promise.reject()    .then(function success() {      throw 'I must not be executed';    })    .catch(done);});it('also .catch() returns a promise, allowing promise chaining'  , (done) => {  Promise.reject()    .catch(() => undefined)    .then(done);});it('you must return a rejected promise if you want to ' +  'execute the next fail callback', (done) => {  function someApiCall() {    return Promise.reject('Error');  }  someApiCall()    .catch((err) => {      console.error(err);      // Without the line below, .catch gets not called      return Promise.reject(err);    })    .catch(done);});it('... or you can throw an error if you want to ' +  'execute the next fail callback', (done) => {  function someApiCall() {    return Promise.reject('Error');  }  someApiCall()    .catch((err) => {      console.error(err);      throw err; // Without this line, .catch gets not called    })    .catch(done);});it('values returned inside .then()/.catch() callbacks ' +  'are provided to the next callback', (done) => {  Promise.resolve(1)    .then(value => value + 1)    .then(value => expect(value).toBe(2));  Promise.reject(1)    .catch(value => value + 1)    .then(value => expect(value).toBe(2));  setTimeout(() => {    done();  }, 1);});

Хорошо, но какие Promise.resolve() и Promise.reject() ? Давай узнаем!

it('you can use Promise.resolve() to wrap values or promises'  , (done) => {  function iMayReturnAPromise() {    return Math.random() >= 0.5 ? Promise.resolve() : 5;  }
  Promise.resolve(iMayReturnAPromise()).then(done);});
it('you can use Promise.resolve() to execute something just after'  , (done) => {  let arr = [];  Promise.resolve().then(() => arr.push(2));  arr.push(1);
  setTimeout(() => {    expect(arr).toEqual([1, 2]);    done();  }, 1);});
/** @see **/it('Promise.resolve() is normally executed before setTimeout(.., 0)'  , (done) => {  let arr = [];  setTimeout(() => arr.push('timeOut'), 0);  Promise.resolve().then(() => {    arr.push('resolve');  });
  setTimeout(() => {    expect(arr).toEqual(['resolve', 'timeOut']);    done();  }, 1);});
it('you can create rejected promises', (done) => {  Promise.reject('reason').catch(done);});
it('pay attention to "Uncaught (in promise) ..."', () => {  Promise.reject('The error');  // Outputs in the console Uncaught (in promise) The error});

Объединение обещаний против создания новых

Хотя new Promise(...) это способ создать обещание, вам следует избегать его использования. В большинстве случаев функции/библиотеки возвращают обещания, поэтому вам следует цепочкой обещать, а не создавать новые:

it("Don't use new Promise(...), prefer chaining", (done) => {  const url="  function badlyDesignedCustomFetch() {    return new Promise((resolve, reject) => {      fetch(url).then((response) => {        if (response.ok) {          resolve(response);        } else {          reject('Fetch failed');        }      });    });  }  function wellDesignedCustomFetch() {    return fetch(url).then((response) =>; {      if (!response.ok) {        return Promise.reject('Fetch failed');      }      return (response);    });  }  Promise.all([    badlyDesignedCustomFetch(),    wellDesignedCustomFetch()  ]).then(done);});

Но когда следует использовать new Promise(...) ? Когда вы хотите перейти от интерфейса обратного вызова к интерфейсу Promise. Смотрите ниже:

function imgOnLoad(img) {  return new Promise((resolve, reject) => {    img.onload = resolve;    img.onerror = reject;  });}

Параллельное исполнение

Сочетание обещаний — это хорошо, но как насчет параллельного выполнения асинхронных операций? Ниже приведено все, что вам нужно знать:

// Parallel execution of promises
it('you can use Promise.all([...]) to execute promises in parallel'  , (done) => {  const url="  const p1 = fetch(`${url}/1`);  const p2 = fetch(`${url}/2`);  Promise.all([p1, p2])    .then(([res1, res2]) => {      return Promise.all([res1.json(), res2.json()])    })    .then(([post1, post2]) => {      expect(post1.id).toBe(1);      expect(post2.id).toBe(2);    })    .then(done);});it('Promise.all([...]) will fail if any of the promises fails'  , (done) => {  const p1 = Promise.resolve(1);  const p2 = Promise.reject('Error');  Promise.all([p1, p2])    .then(() => {      fail('I will not be executed')    })    .catch(done);});it("if you don't want Promise.all() to fail, wrap the promises " +  "in a promise that will not fail", (done) => {  function iMayFail(val) {    return Math.random() >= 0.5 ?      Promise.resolve(val) :      Promise.reject(val);  }  function promiseOr(p, value) {    return p.then(res => res, () => value);  }  const p1 = iMayFail(10);  const p2 = iMayFail(9);  Promise.all([promiseOr(p1, null), promiseOr(p2, null)])    .then(([val1, val2]) => {      expect(val1 === 10 || val1 === null).toBe(true);      expect(val2 === 9 || val2 === null).toBe(true);    })    .catch(() => {      fail('I will not be executed')    })    .then(done);});it('Promise.race([...]) will resolve as soon as ' +  'one of the promises resolves o rejects', (done) => {  const timeout =    new Promise((resolve, reject) => setTimeout(reject, 100));  const data =    fetch(';  Promise.race([data, timeout])    .then(() => console.log('Fetch OK'))    .catch(() => console.log('Fetch timeout'))    .then(done);});

Синтаксис

Синтаксис Promise несколько сложный по сравнению с типичным синтаксисом синхронного кода. Это правда, что с помощью цепочки обещаний код сохраняет хорошую читабельность, но это могло быть лучше. Новый синтаксис await/async делает использование обещаний таким же простым, как написание синхронного кода.

// New await/async syntax
it('you can use the new await/async syntax', async () => {  function timeout(ms) {    return new Promise((resolve) => setTimeout(resolve, ms));  }  const start = Date.now();  const delay = 200;  await timeout(delay + 2); // Just some ms tolerance  expect(Date.now() - start).toBeGreaterThanOrEqual(delay);});it('an async function returns a promise', (done) => {  async function iAmAsync() {    return 1;  }  iAmAsync()    .then((val) => expect(val).toBe(1))    .then(done);});it('await just awaits a promise resolution', async (done) => {  await Promise.resolve();  done();});it('await will throw an error if the promise fail', async(done) => {  try {    await Promise.reject();    fail('I will not be executed');  } catch (err) {    done();  }});

Синхронные функции

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

Все тесты здесь, на JSFiddle.

Это все! Надеюсь, вам понравилась эта статья.

Добавить комментарий

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