Асинхронный режим предназначен для увеличения загрузки процессора и поддержания его постоянной занятости.
Некоторые операции (наиболее типичная из них — ввод-вывод) не требуют участия ЦП и отнимают очень много времени. Если асинхронный режим не используется, он формирует состояние блокировки, что приводит к простою ЦП и зависанию страницы.
Когда операция ввода-вывода происходит в асинхронной среде, ЦП откладывает работу ввода-вывода (в это время ввод-вывод берет на себя другие контроллеры, а данные все еще передаются), а затем обрабатывает следующую задачу. , ожидая завершения операции ввода-вывода. Уведомите ЦП (обратный вызов — это метод уведомления), чтобы он вернулся к работе.
Основное содержание «Асинхронный вызов и обратный вызов JavaScript» заключается в том, что конкретное время окончания асинхронной работы не определено. Для точного выполнения последующей обработки после завершения асинхронной работы необходимо передать обратный вызов в асинхронную функцию, чтобы After. Завершив работу, продолжите выполнение следующих задач.
Хотя обратные вызовы можно очень просто реализовать асинхронно, из-за множественной вложенности они могут превратиться в ад обратного вызова. Чтобы избежать ада обратных вызовов, вам необходимо преобразовать вложенное программирование в линейное.
Promise
— лучшее решение для борьбы с адом обратных вызовов в JavaScript
.
Promise
можно перевести как «обещание». Мы можем инкапсулировать асинхронную работу и назвать ее Promise
, то есть дать обещание и пообещать дать четкий сигнал после завершения асинхронной работы!
Синтаксис Promise
:
let Promise = new Promise(function(resolve,reject){ // Асинхронная работа})
С помощью приведенного выше синтаксиса мы можем инкапсулировать асинхронную работу в Promise
. Функция, передаваемая при создании Promise
, — это метод обработки асинхронной работы, также известный как executor
.
resolve(result)
result
error
reject(error)
resolve
reject
предоставляемые самим JavaScript
. Их можно вызвать, когда executor
завершает задачу:
error
;executor
автоматически выполнится сразу после создания Promise
, и его статус выполнения изменит состояние внутренних свойств Promise
:
state
— изначально pending
, затем преобразуется в fulfilled
после вызова resolve
, или становится rejected
при вызове reject
;result
— — Первоначально undefined
, а затем становится value
после вызова методаsolve resolve(value)
или становится error
после вызова reject
.fs.readFile
асинхронной функцией; Мы можем передать его в executor
. Операции чтения файла выполняются в файле, тем самым инкапсулируя асинхронную работу.
Следующий код инкапсулирует функцию fs.readFile
и resolve(data)
для обработки успешных результатов и reject(err)
для обработки неудачных результатов.
Код выглядит следующим образом:
пусть обещание = новое обещание((разрешить, отклонить) => { fs.readFile('1.txt', (ошибка, данные) => { console.log('Прочитать 1.txt') если (ошибка) отклонить (ошибка) решить (данные) })})
Если мы выполним этот код, то будут выведены слова «Read 1.txt», доказывающие, что операция чтения файла выполняется сразу после создания Promise
.
Promise
обычно инкапсулирует асинхронный код внутри, но он инкапсулирует не только асинхронный код.
Приведенный выше случай Promise
инкапсулирует операцию чтения файла. Файл будет прочитан сразу после завершения создания. Если вы хотите получить результат выполнения Promise
, вам нужно использовать три метода then
, catch
и finally
.
Метод then
Promise
может использоваться для обработки работы после завершения выполнения Promise
. Он получает два параметра обратного вызова. Синтаксис следующий:
обещание.then(function(result),function(error))
result
— это значение, полученное функцией resolve
;error
параметра — это параметр, полученный функцией reject
,например:
let;
обещание = новое обещание((разрешить, отклонить) => { fs.readFile('1.txt', (ошибка, данные) => { console.log('Прочитать 1.txt') если (ошибка) отклонить (ошибка) решить (данные) })})обещайте.то( (данные) => { console.log('Успешно выполнено, результат' + data.toString()) }, (ошибка) => { console.log('Выполнение не выполнено, ошибка' + err.message) })
Если чтение файла выполнено успешно, будет вызвана первая функция:
PS E:CodeNodedemos 3-callback> node .index.js Прочитать 1.txt В случае успешного выполнения результатом будет 1
удаленный 1.txt
. Если выполнение завершится неудачно, будет вызвана вторая функция:
PS E:CodeNodedemos 3-callback> node .index.js. Прочитать 1.txt Выполнение завершилось с ошибкой ENOENT: нет такого файла или каталога, откройте «E:CodeNodedemos 3-callback1.txt».
Если мы сосредоточимся только на результате успешного выполнения, мы сможем передать только один функция обратного вызова:
обещание .then((data)=>{ console.log('Успешно выполнено, результат' + data.toString())})
На этом этапе мы реализовали асинхронную операцию чтения файла.
Если мы сосредоточимся только на результате сбоя, мы можем сначала передать null
, then
обратный вызов: promise.then(null,(err)=>{...})
.
Или используйте более promise.catch((err)=>{...})
способ
: fs.readFile('1.txt', (ошибка, данные) => { console.log('Прочитать 1.txt') если (ошибка) отклонить (ошибка) решить (данные) })})promise.catch((err)=>{ console.log(err.message)})
.catch((err)=>{...})
и then(null,(err)=>{...})
имеют точно такой же эффект.
.finally
— это функция, которая будет выполняться независимо от результата promise
. Она имеет ту же цель, что finally
в синтаксисе try...catch...
, и может обрабатывать операции, не связанные с результатом.
Например:
new Promise((resolve,reject)=>{ //что-то...}).finally(()=>{console.log('Выполнить независимо от результата')}).then(result=>{...}, err=>{...} )Обратный вызов «finally» не имеет параметров
fs.readFile()
последовательно считывает 10 файлов и выводит содержимое. из десяти файлов последовательно.
Поскольку fs.readFile()
сам по себе является асинхронным, мы должны использовать вложенность обратных вызовов. Код выглядит следующим образом:
fs.readFile('1.txt', (err, data) => {. console.log(data.toString()) //1 fs.readFile('2.txt', (ошибка, данные) => { console.log(data.toString()) fs.readFile('3.txt', (ошибка, данные) => { console.log(data.toString()) fs.readFile('4.txt', (ошибка, данные) => { console.log(data.toString()) fs.readFile('5.txt', (ошибка, данные) => { console.log(data.toString()) fs.readFile('6.txt', (ошибка, данные) => { console.log(data.toString()) fs.readFile('7.txt', (ошибка, данные) => { console.log(data.toString()) fs.readFile('8.txt', (ошибка, данные) => { console.log(data.toString()) fs.readFile('9.txt', (ошибка, данные) => { console.log(data.toString()) fs.readFile('10.txt', (ошибка, данные) => { console.log(data.toString()) // ==> Врата Ада}) }) }) }) }) }) }) }) })})
Хотя приведенный выше код может выполнить задачу, по мере увеличения вложенности вызовов уровень кода становится глубже, а сложность обслуживания возрастает, особенно когда мы используем реальный код, который может содержать множество циклов и условных операторов вместо. простой console.log(...)
в примере.
Если мы не будем использовать обратные вызовы и напрямую вызовем fs.readFile()
последовательно в соответствии со следующим кодом, что произойдет?
//Примечание: это неправильный способ записи fs.readFile('1.txt', (err, data) => { console.log(data.toString())})fs.readFile('2.txt', (ошибка, данные) => { console.log(data.toString())})fs.readFile('3.txt', (ошибка, данные) => { console.log(data.toString())})fs.readFile('4.txt', (ошибка, данные) => { console.log(data.toString())})fs.readFile('5.txt', (ошибка, данные) => { console.log(data.toString())})fs.readFile('6.txt', (ошибка, данные) => { console.log(data.toString())})fs.readFile('7.txt', (ошибка, данные) => { console.log(data.toString())})fs.readFile('8.txt', (ошибка, данные) => { console.log(data.toString())})fs.readFile('9.txt', (ошибка, данные) => { console.log(data.toString())})fs.readFile('10.txt', (ошибка, данные) => { console.log(data.toString())})
Ниже приведены результаты моего теста (результаты каждого выполнения разные):
PS E:CodeNodedemos 3-callback> node .index.Причина, по которой
js12346957108
выдает этот непоследовательный результат, является асинхронным , а не многопоточным. Асинхронный параллелизм может быть достигнут в одном потоке.
Причина, по которой здесь используется этот случай ошибки, заключается в том, чтобы подчеркнуть концепцию асинхронности. Если вы не понимаете, почему возникает такой результат, вы должны вернуться и наверстать упущенное!
Идея использования Promise
для решения проблемы асинхронного последовательного чтения файлов:
promise1
и используйте resolve
для возврата результата.promise1.then
для получения и вывода результата чтения файла.promise2
.обещании1.then
promise1.then
И вернитесьpromise2.then
, чтобы получить и вывести результат чтенияpromise3
в promise2.then
и вернитесь кpromise3.then
, чтобы получить и вывести результат чтениякод выглядит следующим образом:
пусть обещание1 = новое обещание ((разрешить, отклонить) => { fs.readFile('1.txt', (ошибка, данные) => { если (ошибка) отклонить (ошибка) решить (данные) })}) пусть обещание2 = обещание1.тогда( данные => { console.log(data.toString()) вернуть новое обещание((разрешить, отклонить) => { fs.readFile('2.txt', (ошибка, данные) => { если (ошибка) отклонить (ошибка) решить (данные) }) }) })пусть обещание3 = обещание2.тогда( данные => { console.log(data.toString()) вернуть новое обещание((разрешить, отклонить) => { fs.readFile('3.txt', (ошибка, данные) => { если (ошибка) отклонить (ошибка) решить (данные) }) }) })пусть обещание4 = обещание3.то( данные => { console.log(data.toString()) //..... })... ...
Таким образом, мы записываем исходный вложенный ад обратного вызова в линейный режим.
Но есть еще проблема с кодом. Хоть код и стал красивее в плане управления, но сильно увеличивает длину кода.
Приведенный выше код слишком длинный. Мы можем сократить объем кода за два шага:
promise
и связать .then
код следующим образом:
function myReadFile (path) { вернуть новое обещание((разрешить, отклонить) => { fs.readFile(путь, (ошибка, данные) => { если (ошибка) отклонить (ошибка) console.log(data.toString()) решать() }) })}myReadFile('1.txt') .then(data => { return myReadFile('2.txt') }) .then(data => { return myReadFile('3.txt') }) .then(data => { return myReadFile('4.txt') }) .then(data => { return myReadFile('5.txt') }) .then(data => { return myReadFile('6.txt') }) .then(data => { return myReadFile('7.txt') }) .then(data => { return myReadFile('8.txt') }) .then(data => { return myReadFile('9.txt') }) .then(data => { return myReadFile('10.txt') })
Поскольку метод myReadFile
вернет новое Promise
, мы можем напрямую выполнить метод .then
. Этот метод программирования называется цепным программированием .
Результат выполнения кода следующий:
PS E:CodeNodedemos 3-callback> node .index.js12345678910
На этом операция асинхронного и последовательного чтения файлов завершается.
Примечание. Новый объект
Promise
должен быть возвращен в методе.then
каждого шага, в противном случае будет получен предыдущий старыйPromise
.Это связано с тем, что каждый метод
then
будет продолжать передавать своеPromise
вниз.