Расскажите об асинхронном программировании в javascript.

Node.js внешний интерфейс JavaScript Promise

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

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

图片来自网络

Преимущество этого режима в простоте реализации и относительной простоте среды выполнения Недостаток в том, что пока одна задача выполняется долго, последующие задачи должны будут ждать в очереди, что приведет к задержке выполнения всю программу. Обычные браузеры не отвечают (притворная смерть), часто потому, что определенный фрагмент кода Javascript выполняется в течение длительного времени (например, бесконечный цикл), в результате чего вся страница зависает на этом месте, и другие задачи не могут быть выполнены.

Для решения этой проблемы язык Javascript разделяет режимы выполнения задач на два типа: синхронный и асинхронный.

«Синхронизация» — это то, о чем я говорил выше, последняя задача дожидается окончания предыдущей задачи, а затем выполняет ее.

Что такое "асинхронный"?

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

Ниже представлена ​​блок-схема при наличии трех задач ABC, выполняемых синхронно или асинхронно:

Синхронизировать

thread ->|----A-----||-----B-----------||-------C------|

асинхронный:

A-Start ---------------------------------------- A-End   
           | B-Start ----------------------------------------|--- B-End   
           |   |     C-Start -------------------- C-End      |     |   
           V   V       V                           V         V     V      
  thread-> |-A-|---B---|-C-|-A-|-C-|--A--|-B-|--C--|---A-----|--B--|

«Асинхронность» очень важна. На стороне браузера операции, которые занимают много времени, должны выполняться асинхронно, чтобы избежать зависания браузера.Лучший пример — операции Ajax. На стороне сервера «асинхронный режим» является даже единственным режимом, потому что среда выполнения является однопоточной, и если разрешить синхронное выполнение всех http-запросов, производительность сервера резко упадет и быстро перестанет отвечать.

В этой статье кратко изложена история развития асинхронных функций JavaScript, как показано ниже:

图片来自网络

  1. Перезвоните
  2. Promise
  3. Generator+co
  4. async,await

Функция обратного вызова

Кажется, что все должно начинаться с callback-функции.

Асинхронный JavaScript

В Javascript асинхронное программирование может быть выполнено только через первоклассные функции в JavaScript: таким образом мы можем передать функцию в качестве аргумента другой функции, а внутри этой функции мы можем вызвать переданную функцию (то есть функцию обратного вызова).

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

Функция обратного вызова не имеет возвращаемого значения (не пытайтесь использовать return), она используется только для выполнения некоторого действия внутри функции.

См. пример ниже:

step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                // Do something with value4
            });
        });
    });
});

Здесь всего 4 шага, и вложены 4 слоя обратных вызовов, а если шагов больше? Очевидно, что такой код просто классно писать, но он имеет много недостатков.

Проблемы чрезмерного использования обратных вызовов:

  • Если вы не организуете свой код должным образом, очень легко вызвать ад обратных вызовов, что затруднит понимание вашего кода другими.
  • Cannot catch exception (try catch выполняется синхронно, функция обратного вызова будет добавлена ​​в очередь, а ошибки не могут быть перехвачены)
  • Вы не можете использовать оператор return для возврата значения, и вы также не можете использовать ключевое слово throw.

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

Promise

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

Обещание представляет собой конечный результат асинхронной операции.

Обещание означает [желание | обещание] операцию, которая еще не завершена, но будет сделано в будущем. Основной способ взаимодействия с промисом — получить окончательное значение промиса или причину окончательного отклонения промиса, передав функцию его методу then. Есть три основных момента:

  • Рекурсия, каждая асинхронная операция возвращает обещанный объект
  • Конечный автомат: три перехода состояния, которыми можно управлять только внутри объекта промиса, и состояние нельзя изменить извне
  • глобальная обработка исключений

1) Определение

var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then…

  if (/* everything turned out fine */) {
    resolve("Stuff worked!");
  }
  else {
    reject(Error("It broke"));
  }
});

Каждое определение промиса одинаковое.В конструкторе передается анонимная функция.Параметры разрешения и отклонения представляют собой обработку успеха и отказа соответственно.

2) позвонить

promise.then(function(text){
    console.log(text)// Stuff worked!
    return Promise.reject(new Error('我是故意的'))
}).catch(function(err){
    console.log(err)
})

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

Мы можем узнать о некоторых принципах и характеристиках промисов, назвав пример промисов:

Пример обычного вызова:

let fs = require('fs');
let p = new Promise(function(resolve,reject){
  fs.readFile('./1.txt','utf8',(err,data)=>{
      err?reject(err):resolve(data);
  })
})

p.then((data)=>{console.log(data)},(err)=>{console.log(err)});

1. Экземпляр Promise может вызывать метод многократно:

p.then((data)=>{console.log(data)},(err)=>{console.log(err)});
p.then((data)=>{console.log(data)},(err)=>{console.log(err)});

2. Экземпляры промисов могут поддерживать цепочку вызовов метода then.jQuery реализует цепочку, возвращая текущий this. Но обещания не могут быть выполнены, вернув это. Потому что последующее добавление then через цепочку не определяет успех или неудачу обхода состояния исходного объекта обещания.

p.then((data)=>{console.log(data)},(err)=>{console.log(err)}).then((data)=>{console.log(data)})

3. Пока обратный вызов успеха и обратный вызов отказа в методе then имеют возвращаемое значение (включая undefiend), они перейдут к обратному вызову успеха в следующем методе then и передают возвращаемое значение в качестве параметра следующего затем успеха Перезвоните.

第一个then走成功:
p.then((data)=>{return undefined},(err)={console.log()}).then((data)=>{console.log(data)})
输出:undefiend
第一个then走失败:
  p.then((data)=>{console.log(1)},(err)={return undefined).then((data)=>{console.log(data)})
输出:undefiend

4. Пока один из успешных обратных вызовов и обратных вызовов с ошибкой в ​​методе then выдает исключение, он переходит к обратному вызову с ошибкой в ​​следующем методе then.

第一个then走成功:
p.then((data)=>{throw new Err("错误")},(err)={console.log(1)}).then((data)=>{console.log('成功')},(err)=>{console.log(err)})
输出:错误
第一个then走失败:
  p.then((data)=>{console.log(1)},(err)={throw new Err("错误")).then((data)=>{console.log('成功')},(err)=>{console.log(err)})
输出:错误

5. Успех и неудача могут идти только в одну сторону, если получится, то не пойдет к провалу, если потерпит неудачу, то не пойдет к успеху;

6. Если метод then возвращает не обычное значение, а промис-объект, что делать?

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

7. Имейте функцию catch для перехвата ошибок; если все методы then перед catch не имеют обратного вызова с ошибкой, catch поймает сообщение об ошибке и выполнит его, что используется для итоговой строки.

p是一个失败的回调:
p.then((data)=>{console.log('成功')}).then((data)=>{成功}).catche(e){console.log('错误')}

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

var  r  = new Promise(function(resolve,reject){
   return r;
})
r.then(function(){
    console.log(1)
},function(err){
    console.log(err)
})

Вы можете видеть, что результат всегда ожидает

图片来自网络

Если у вас нет промисов из коробки, вам может понадобиться прибегнуть к какой-нибудь библиотеке промисов, популярным вариантом является использованиеbluebird. Эти библиотеки могут предоставлять больше функциональных возможностей, чем собственное решение, и не ограничиваются функциями, указанными в стандарте Promise/A+.

Generator(ECMAScript6)+co

JavaScript СтроительОтносительно новая концепция, новая для ES6 (также известная как ES2015). Представьте себе сценарий, подобный следующему:

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

Описанный выше сценарий — это именно то, что должны решить функции генератора JavaScript. Когда мы вызываем функцию генератора, она не выполняется немедленно, а требует от нас ручного выполнения итерационной операции (следующий метод). То есть вы вызываете функцию-генератор, и она возвращает вам итератор. Итератор перебирает каждую точку останова.

function* foo () {  
  var index = 0;
  while (index < 2) {
    yield index++; //暂停函数执行,并执行yield后的操作
  }
}
var bar =  foo(); // 返回的其实是一个迭代器

console.log(bar.next());    // { value: 0, done: false }  
console.log(bar.next());    // { value: 1, done: false }  
console.log(bar.next());    // { value: undefined, done: true }  

Идя дальше, если вы хотите использовать функции генератора для более простого написания асинхронного кода JavaScript, мы можем использоватьcoЭта библиотека написана знаменитым tj god.

Co – это инструмент управления потоком на основе генератора для Node.js и браузеров. С помощью Promises вы можете писать неблокирующий код более элегантным способом.

Используя co, код предыдущего примера, мы можем переписать его следующим кодом:

co(function* (){  
  yield Something.save();
}).then(function() {
  // success
})
.catch(function(err) {
  //error handling
});

Вы спросите: как реализовать параллельную работу? Ответ может быть проще, чем вы думаете, следующим образом (на самом деле это Promise.all):

yield [Something.save(), Otherthing.save()];  

Окончательное решение Async/await

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

За кулисами асинхронные функции фактически используют обещания, поэтому асинхронные функции возвращают обещание.

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

async function save(Something) {  
  try {
    await Something.save(); // 等待await后面的代码执行完,类似于yield
  } catch (ex) {
    //error handling
  }
  console.log('success');
} 

Чтобы использовать асинхронные функции, вам необходимо указать перед объявлением функции ключевое слово async. После этого вы можете использовать ключевое слово await внутри функции, эффект аналогичен предыдущему эффекту yield.

Использование асинхронных функций для выполнения параллельных задач очень похоже на yield, с той лишь разницей, что Promise.all в настоящее время больше не является неявным, вам нужно вызывать его явно:

async function save(Something) {  
    await Promise.all[Something.save(), Otherthing.save()]
}

Async/Await — идеальное решение для асинхронных операций.Коа 2 выпустила официальную версию сразу после выпуска узла 7.6, и рекомендуется использовать асинхронные функции для написания промежуточного программного обеспечения Коа.

Вот фрагмент кода из приложения Koa 2:

exports.list = async (ctx, next) => {
  try {
    let students = await Student.getAllAsync();
  
    await ctx.render('students/index', {
      students : students
    })
  } catch (err) {
    return ctx.api_error(err);
  }
};

он делает 3 вещи

  • Получите информацию обо всех студентах с помощью await Student.getAllAsync();.
  • Рендерим страницу через await ctx.render
  • Поскольку это синхронный код, используйте try/catch для обработки исключений.

Позже я поделюсь основными понятиями node и eventLoop (макрозадачи и микрозадачи).

(над)

Ссылаться на:The Evolution of Asynchronous JavaScript