Большое продвижение интерфейса (два) | Асинхронное программирование JavaScript

JavaScript
Большое продвижение интерфейса (два) | Асинхронное программирование JavaScript

Наймите рабочих! Рабочий дух! Передняя часть лучшая! Эта серия записана в процессе обучения переднего тренировочного лагеря Lagou Education.Если в статье что-то не так, я надеюсь, что все могут покритиковать и исправить это и добиться прогресса друг с другом.

Классические вопросы для интервью

Давайте сначала рассмотрим классический вопрос интервью, и пусть наши маленькие мозги обдумают его ~ Если у вас есть четкое представление об этом вопросе и вы понимаете причину, стоящую за ним, тогда, пожалуйста, ставьте лайк и комментируйте прямо и подписывайтесь! ! ! ! !

//请写出输出内容
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
	console.log('async2');
}

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)

async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

Отвечать! Вы поняли это правильно? Не беги, если что-то не так, открой глазки и внимательно прочитай следующее

/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/

Причина, по которой JS работает в однопоточном режиме

Чтобы ответить на этот вопрос, нам сначала нужно знать JS.Среда исполнения однопоточнаяДа, поскольку язык JS изначально был языком, работающим на стороне браузера, цель состоит в том, чтобы добиться динамического взаимодействия на странице. Ядром реализации динамического взаимодействия является работа с DOM, поэтому определено, что JS должен работать в однопоточном режиме. Предположим, что если JS работает с несколькими потоками, один поток модифицирует элемент DOM, а другой поток одновременно хочет удалить элемент DOM, то браузер в это время находится в замешательстве, и невозможно узнать, какой рабочий поток использовать. Поэтому, чтобы избежать проблемы синхронизации потоков, JS спроектирован как однопоточный режим работы.

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

Преимущества и недостатки однопоточности

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

Синхронный режим и асинхронный режим

Режим синхронизации

Режим синхронизации на самом деле очень прост для понимания, например:

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

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

Асинхронный режим

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

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

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

Цикл событий EventLoop и очередь сообщений

  • EventLoopЭто циклический механизм: основной поток считывает задачи из очереди сообщений и выполняет их последовательно, этот процесс не прерывается.
  • очередь сообщенийЭто место для хранения асинхронных задач.Когда все наши синхронные задачи будут выполнены, EventLoop будет последовательно брать асинхронные задачи из очереди сообщений и помещать их в стек вызовов для выполнения.

Макрозадачи и микрозадачи

  • задача макросаМожно понять, что код, выполняемый каждый раз при выполнении стека, является макрозадачей.

    Чтобы обеспечить упорядоченное выполнение внутренних макрозадач JS и операций DOM, браузер будет повторно отображать страницу после завершения выполнения одной макрозадачи и до начала выполнения следующей макрозадачи.

    Задачи макросов включают в себя: общий код скрипта, setTimeout, setInterval, ввод-вывод, события взаимодействия с пользовательским интерфейсом, MessageChannel и т. д.

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

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

    К микрозадачам относятся: Promise.then, MutaionObserver, process.nextTick (в среде Node.js) и т. д.

Картинка взята с Наггетс, и вторжение будет удалено сразу

Суть схемы асинхронного программирования - функция обратного вызова

Функция обратного вызова: функция, настроенная вызывающей стороной и переданная исполнителю для выполнения.

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

// callback就是回调函数
// 就是把函数作为参数传递,缺点是不利于阅读,执行顺序混乱。
function foo(callback) {
    setTimeout(function(){
        callback()
    }, 3000)
}

foo(function() {
    console.log('这是回调函数')
    console.log('调用者定义这个函数,执行者执行这个函数')
    console.log('其实就是调用者告诉执行者异步任务结束后应该做什么')
})

Лучшее унифицированное решение для пошагового программирования - Promise

Обзор промисов

Концепция обещания Портал MDN

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

// 我们想要执行完第一个再执行第二个再执行第三个
// 虽然我们使用同步的方式将异步的代码学出来了,但是这样的回调是不是让我们的小脑袋瓜子嗡嗡的?
setTimeout(() => {
    console.log('执行第一个');
    setTimeout(() => {
        console.log('执行第二个');
        setTimeout(() => {
            console.log('执行第三个');
            setTimeout(() => {
                console.log('执行第四个');
                setTimeout(() => {
                    console.log('执行第五个');
                }, 2000);
            }, 2000);
        }, 2000);
    }, 2000);
}, 2000);

Иконка callback hell, взятая из сети, сразу удаляйте.

Основное использование Promise

// Promise 基本示例
// promise的英文意思是承诺
// 在JS中Promise是一个对象,接收一个函数作为参数
const promise = new Promise(function (resolve, reject) {
  // 这里用于“兑现”承诺
  // resolve(100) // 承诺达成
  reject(new Error('promise rejected')) // 承诺失败
})

promise.then(function (value) {
  // 即便没有异步操作,then 方法中传入的回调仍然会被放入队列,等待下一轮执行
  console.log('resolved', value)
}, function (error) {
  console.log('rejected', error)
})

Обещание дела

Мы используем Promise для инкапсуляции AJax

function ajax (url) {
  return new Promise((resolve, rejects) => {
    // 创建一个XMLHttpRequest对象去发送一个请求
    const xhr = new XMLHttpRequest()
    // 先设置一下xhr对象的请求方式是GET,请求的地址就是参数传递的url
    xhr.open('GET', url)
    // 设置返回的类型是json,是HTML5的新特性
    // 我们在请求之后拿到的是json对象,而不是字符串
    xhr.responseType = 'json'
    // html5中提供的新事件,请求完成之后(readyState为4)才会执行
    xhr.onload = () => {
      if(this.status === 200) {
        // 请求成功将请求结果返回
        resolve(this.response)
      } else {
        // 请求失败,创建一个错误对象,返回错误文本
        rejects(new Error(this.statusText))
      }
    }
    // 开始执行异步请求
    xhr.send()
  })
}

ajax('/api/user.json').then((res) => {
  console.log(res)
}, (error) => {
  console.log(error)
})

Цепочка обещаний

Непонимание

  • Вложенность — наиболее распространенное заблуждение относительно использования промисов. Мы хотим использовать цепной метод промисов, чтобы обеспечить максимально возможное выравнивание асинхронных задач.
// 嵌套使用 Promise 是最常见的误区
ajax('/api/urls.json').then(function (urls) {
  ajax(urls.users).then(function (users) {
    ajax(urls.users).then(function (users) {
      ajax(urls.users).then(function (users) {
        ajax(urls.users).then(function (users) {
        })
      })
    })
  })
})

Понимание связанных вызовов

  • Метод then объекта обещания возвращает совершенно новый объект обещания. Вы можете продолжать вызывать метод then. Если возвращаемым значением является не объект promise, а значение, то это значение будет передано в качестве значения разрешения. Если значение отсутствует, по умолчанию используется значение undefined
  • Следующий метод then заключается в регистрации обратного вызова для обещания, возвращенного предыдущим then.
  • Возвращаемое значение функции обратного вызова в предыдущем методе then будет использоваться в качестве параметра обратного вызова последующего метода then.
  • Если обещание возвращается в обратном вызове, обратный вызов метода then будет ждать его завершения.

Обработка исключений промисов

  • Затем метод обратного вызова onRejected
  • .catch()
ajax('/api/user.json')
  .then(function onFulfilled(res) {
    console.log('onFulfilled', res)
  })
  .catch(function onRejected(error) {
    console.log('onRejected', error)
  })
  
// 相当于
ajax('/api/user.json')
  .then(function onFulfilled(res) {
    console.log('onFulfilled', res)
  })
  .then(undefined, function onRejected(error) {
    console.log('onRejected', error)
  })

Короче говоря, .catch — это обратный вызов с ошибкой, зарегистрированный для всей цепочки промисов, и его рекомендуется использовать.

Другие статьи из этой серии Портал

Ссылаться на

Потяните за крюк большой передний тренировочный лагерь