Асинхронное решение ---- Обещай и жди

внешний интерфейс JavaScript Promise Ajax

предисловие

Режим асинхронного программирования становится все более важным в процессе разработки интерфейса. С самого начала XHR до инкапсулированного Ajax пытаются решить проблемы в процессе асинхронного программирования. С появлением нового стандарта ES6 появились новые решения для обработки асинхронных потоков данных. Все мы знаем, что в традиционных ajax-запросах, когда существует зависимость данных между асинхронными запросами, могут генерироваться уродливые многоуровневые обратные вызовы, широко известные как «ад обратных вызовов», что обескураживает.Появление Promise позволяет нам попрощаться с функциями обратного вызова и написать более элегантный асинхронный код.. В процессе практики я обнаружил, что Promise не идеален, Async/Await — одна из самых революционных фич, добавленных в JavaScript за последние годы.Async/Await предоставляет альтернативу созданию асинхронного кода, похожего на синхронный код.. Далее мы вводим эти две схемы для работы с асинхронным программированием.

1. Принцип и основной синтаксис Promise

1. Принцип обещания

Promise — это инкапсуляция асинхронных операций, которая может добавлять методы, которые будут выполняться при успешном или неудачном выполнении асинхронной операции через независимый интерфейс. Основной спецификацией является Promises/A+.

В промисе есть несколько состояний:

  • В ожидании: начальное состояние, не выполненное или отклонено;

  • выполнено: успешная операция, для удобства выражения, выполнено заменено на разрешено;

  • отклонено: операция, которая не удалась.

Ожидание может быть преобразовано в выполненное или отклоненное и может быть преобразовано только один раз, то есть, если ожидание преобразовано в выполненное состояние, оно не может быть снова преобразовано в отклоненное. А выполненное и отклоненное состояния могут быть преобразованы только из ожидающих и не могут быть преобразованы друг в друга.

2. Базовый синтаксис Promise

  • Экземпляры промисов должны реализовывать метод then

  • then() должен иметь возможность принимать две функции в качестве аргументов

  • then() должен возвращать экземпляр Promise

<script src="https://cdn.bootcss.com/bluebird/3.5.1/bluebird.min.js"></script>//如果低版本浏览器不支持Promise,通过cdn这种方式
      <script type="text/javascript">
        function loadImg(src) {
            var promise = new Promise(function (resolve, reject) {
                var img = document.createElement('img')
                img.onload = function () {
                    resolve(img)
                }
                img.onerror = function () {
                    reject('图片加载失败')
                }
                img.src = src
            })
            return promise
        }
        var src = 'https://www.imooc.com/static/img/index/logo_new.png'
        var result = loadImg(src)
        result.then(function (img) {
            console.log(1, img.width)
            return img
        }, function () {
            console.log('error 1')
        }).then(function (img) {
            console.log(2, img.height)
        })
     </script>

2. Несколько последовательных операций Promise

Promise также может делать больше вещей, например, есть несколько асинхронных задач, вам нужно сначала выполнить задачу 1, в случае успеха, затем выполнить задачу 2, если какая-либо задача не удалась, она не будет продолжаться и выполнять функцию обработки ошибок. Чтобы выполнять такие асинхронные задачи последовательно, без промисов, вам нужно писать слои вложенного кода.

С промисами мы просто пишемjob1.then(job2).then(job3).catch(handleError);Где job1, job2 и job3 являются объектами Promise.

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

       var src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
        var result1 = loadImg(src1) //result1是Promise对象
        var src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
        var result2 = loadImg(src2) //result2是Promise对象
        result1.then(function (img1) {
            console.log('第一个图片加载完成', img1.width)
            return result2  // 链式操作
        }).then(function (img2) {
            console.log('第二个图片加载完成', img2.width)
        }).catch(function (ex) {
            console.log(ex)
        })

Здесь следует отметить следующее:Один и тот же промис может вызывать метод THEN несколько раз, тогда метод должен возвращать объект промиса.. В приведенном выше примере, если result1.then не возвращает экземпляр Promise в открытом виде, он по умолчанию использует свой собственный экземпляр Promise, являющийся результатом1, а result1.then возвращает экземпляр результата2, который выполняется позже, а затем фактически выполняет результат2. тогда

3. Общие методы Обещания

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

Представьте систему страничного чата. Нам нужно получить личную информацию пользователя и список друзей с двух разных URL. Эти две задачи можно выполнять параллельно, используя Promise.all() для достижения следующего:

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
    console.log(results); // 获得一个Array: ['P1', 'P2']
});

Иногда несколько асинхронных задач необходимы для обеспечения отказоустойчивости. Например, чтобы прочитать личную информацию пользователя с двух URL-адресов одновременно, вам нужно только сначала получить результат. В этом случае используйте Promise.race() для реализации:

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P1'
});

Поскольку p1 выполняется быстрее, функция then() в Promise получит результат 'P1'. p2 продолжает выполняться, но результат выполнения будет отброшен.

Резюме: Promise.all принимает массив объектов промисов и после того, как все они завершены, равномерно выполняет успех.;

Promise.race принимает массив из нескольких объектов обещаний и успешно выполняется до тех пор, пока один из них не завершится.

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

     var src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
     var result1 = loadImg(src1)
     var src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
     var result2 = loadImg(src2)
     Promise.all([result1, result2]).then(function (datas) {
         console.log('all', datas[0])//<img src="https://www.imooc.com/static/img/index/logo_new.png">
         console.log('all', datas[1])//<img src="https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg">
     })
     Promise.race([result1, result2]).then(function (data) {
         console.log('race', data)//<img src="https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg">
     })

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

4. Введение и использование Async/Await

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

1. Введение в Async/Await

  • async/await — это новый способ написания асинхронного кода, лучше, чем функции обратного вызова и промисы.

  • async/await реализован на основе Promise, его нельзя использовать для обычных callback-функций.

  • async/await, как и Promises, неблокирует.

  • async/await делает асинхронный код похожим на синхронный, без обратных вызовов. Но это не меняет однопоточной, асинхронной природы JS.

2. Использование Async/Await

  • С ожиданием функция должна быть помечена как async

  • ожидание сопровождается экземпляром обещания

  • Необходимо установить babel-polyfill, не забудьте импортировать //npm i --save-dev babel-polyfill после установки

   function loadImg(src) {
            const promise = new Promise(function (resolve, reject) {
                const img = document.createElement('img')
                img.onload = function () {
                    resolve(img)
                }
                img.onerror = function () {
                    reject('图片加载失败')
                }
                img.src = src
            })
            return promise
        }
     const src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
     const src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
     const load = async function(){
        const result1 = await loadImg(src1)
        console.log(result1)
        const result2 = await loadImg(src2)
        console.log(result2) 
     }
     load()

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

5. Обработка ошибок Async/Await

Объект Promise после команды await может быть отклонен, поэтому команду await лучше поместить в блок кода try...catch.Обработка ошибок try..catch также больше соответствует логике, которую мы обычно используем при написании синхронного кода..

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

6. Почему Async/Await лучше?

Async/Await имеет много преимуществ перед Promise, три из которых описаны ниже:

1. Краткость

Использование Async/Await, очевидно, экономит много кода. Нам не нужно писать .then, нам не нужно писать анонимные функции для обработки значения разрешения Promise, нам не нужно определять избыточные переменные данных, и мы избегаем вложенного кода.

2. Промежуточное значение

Вы, вероятно, сталкивались со сценарием, в котором вызывается promise1, вызывается promise2 с результатом, возвращаемым promise1, а promise3 вызывается с результатом обоих. Ваш код, скорее всего, будет выглядеть так:

const makeRequest = () => {
  return promise1()
    .then(value1 => {
      return promise2(value1)
        .then(value2 => {        
          return promise3(value1, value2)
        })
    })
}

Использование async/await делает код невероятно простым и интуитивно понятным.

const makeRequest = async () => {
  const value1 = await promise1()
  const value2 = await promise2(value1)
  return promise3(value1, value2)
}

3. Условные операторы

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

const makeRequest = () => {
  return getJSON()
    .then(data => {
      if (data.needsAnotherRequest) {
        return makeAnotherRequest(data)
          .then(moreData => {
            console.log(moreData)
            return moreData
          })
      } else {
        console.log(data)
        return data
      }
    })
}

Вложенность кода (6 уровней) менее читабельна, все, что они сообщают, это то, что конечный результат нужно передать самому внешнему промису. Написание с помощью async/await может значительно улучшить читабельность:

const makeRequest = async () => {
  const data = await getJSON()
  if (data.needsAnotherRequest) {
    const moreData = await makeAnotherRequest(data);
    console.log(moreData)
    return moreData
  } else {
    console.log(data)
    return data    
  }
}

Справочная статья

6 причин, по которым Async/Await заменяет обещания

Promise и Await/Async для интерфейсных асинхронных решений

Учебник по Javascript от Ляо Сюэфэна

[Перевод] Обещания/Спецификация A+

Значение и использование асинхронных функций