Прежде чем изучать содержание этой статьи, мы должны сначала понять концепцию асинхронного кода. Первое, что следует подчеркнуть, — это существенная разница между асинхронным и параллельным .
CPU
, или на нескольких CPU
, или на нескольких физических хостах или даже в нескольких сетях.CPU
временно откладывает текущую задачу, сначала обрабатывает следующую задачу, а затем возвращается к предыдущей задаче для продолжения выполнения после получения уведомления об обратном вызове предыдущей задачи. Весь процесс не требует выполнения. вторая тема участвовать .Возможно, более интуитивно понятно объяснить параллелизм, синхронизацию и асинхронность в виде изображений. Предположим, что есть две задачи A и B, которые необходимо обработать. Методы параллельной, синхронной и асинхронной обработки будут использовать методы выполнения, как показано на рисунке. следующий рисунок:
JavaScript
предоставляет нам множество асинхронных функций. Эти функции позволяют нам удобно выполнять асинхронные задачи. То есть мы начинаем выполнение задачи (функции) сейчас, но задача будет завершена позже, и наступит определенное время завершения. нет Не уверен.
Например, функция setTimeout
— очень типичная асинхронная функция. Кроме того, fs.readFile
и fs.writeFile
также являются асинхронными функциями.
Мы можем сами определить случай асинхронной задачи, например, настроив функцию копирования файлов copyFile(from,to)
:
const fs = require('fs') функция copyFile(от, до) { fs.readFile(from, (ошибка, данные) => { если (ошибка) { console.log(err.message) возвращаться } fs.writeFile(to, data, (ошибка) => { если (ошибка) { console.log(err.message) возвращаться } console.log('Копирование завершено') }) }) }
Функция copyFile
сначала считывает данные файла из параметра from
, а затем записывает данные в файл, на который указывает параметр to
.
Мы можем вызвать copyFile
следующим образом:
copyFile('./from.txt','./to.txt')//Копировать файл.
Если в это время после copyFile(...)
есть другой код, программа не будет wait Выполнение copyFile
завершается, но выполняется непосредственно вниз. Программе не важно, когда завершается задача копирования файла.
copyFile('./from.txt','./to.txt') //Следующий код не будет ждать завершения выполнения приведенного выше кода...
До этого момента все вроде бы нормально, но если мы напрямую обратимся к файлу ./to.txt
после copyFile(...)
функция Что происходит с содержимым в ?
При этом скопированный контент не будет прочитан, просто сделайте следующее:
copyFile('./from.txt','./to.txt') fs.readFile('./to.txt',(err,data)=>{ ... })
Если файл ./to.txt
не был создан перед выполнением программы, вы получите следующую ошибку:
PS E:CodeNodedemos 3-callback> node .index.js
законченный
Копирование завершено
PS E:CodeNodedemos 3-callback> node .index.js
Ошибка: ENOENT: нет такого файла или каталога, откройте «E:CodeNodedemos 3-callbackto.txt»
Копирование завершено.
Даже если ./to.txt
существует, скопированное содержимое невозможно прочитать.
Причина этого явления: copyFile(...)
выполняется асинхронно. После того, как программа выполняет функцию copyFile(...)
, она не ждет завершения копирования, а выполняет ее непосредственно вниз, вызывая файл. появится ошибка ./to.txt
не существует, или ошибка содержимого файла пуста (если файл создан заранее).
Конкретное время окончания выполнения асинхронной функции обратного вызова определить невозможно. Например, время окончания выполнения функции readFile(from,to)
скорее всего, зависит от размера файла from
.
Итак, вопрос в том, как мы можем точно определить конец выполнения copyFile
и прочитать содержимое файла to
?
Для этого необходимо использовать функцию обратного вызова. Мы можем изменить функцию copyFile
следующим образом:
function copyFile(from, to, callback) {. fs.readFile(from, (ошибка, данные) => { если (ошибка) { console.log(err.message) возвращаться } fs.writeFile(to, data, (ошибка) => { если (ошибка) { console.log(err.message) возвращаться } console.log('Копирование завершено') callback()//Функция обратного вызова вызывается после завершения операции копирования}) }) }
Таким образом, если нам нужно выполнить какие-то операции сразу после завершения копирования файла, мы можем записать эти операции в функцию обратного вызова:
function copyFile(from, to, callback) { fs.readFile(from, (ошибка, данные) => { если (ошибка) { console.log(err.message) возвращаться } fs.writeFile(to, data, (ошибка) => { если (ошибка) { console.log(err.message) возвращаться } console.log('Копирование завершено') callback()//Функция обратного вызова вызывается после завершения операции копирования}) }) } copyFile('./from.txt', './to.txt', function () { //Передаем функцию обратного вызова, читаем содержимое файла «to.txt» и выводим fs.readFile('./to.txt', (err, data) => { если (ошибка) { console.log(err.message) возвращаться } console.log(data.toString()) }) })
Если вы подготовили файл ./from.txt
, то приведенный выше код можно запустить напрямую:
PS E:CodeNodedemos 3-callback> node .index.js
Копирование завершено
Присоединяйтесь к сообществу «Xianzong» и культивируйте бессмертие вместе со мной. Адрес сообщества: http://t.csdn.cn/EKf1h
Этот метод программирования называется стилем асинхронного программирования, основанным на обратном вызове. Асинхронно выполняемые функции должны обеспечивать обратный вызов. Параметры: используется для вызова после завершения задачи.
Этот стиль распространен в программировании JavaScript
. Например, все функции чтения файлов fs.readFile
и fs.writeFile
являются асинхронными функциями.
Функция обратного вызова может точно обрабатывать последующие действия после завершения асинхронной работы. Если нам нужно последовательно выполнить несколько асинхронных операций, нам необходимо вложить функцию обратного вызова.
Сценарий:
реализация кода для последовательного чтения файлов A и B:
fs.readFile('./A.txt', (err, data) => { если (ошибка) { console.log(err.message) возвращаться } console.log('Читать файл A: ' + data.toString()) fs.readFile('./B.txt', (ошибка, данные) => { если (ошибка) { console.log(err.message) возвращаться } console.log("Читать файл B: " + data.toString()) }) })
Эффект выполнения:
PS E:CodeNodedemos 3-callback> node .index.js
Чтение файла А: Секта Бессмертных бесконечно хороша, но ему не хватает парня.Чтение файла Б: Если вы хотите присоединиться к Секте Бессмертных, у вас должны быть ссылки.
http://t.csdn.cn/H1faI
С помощью обратного вызова вы можете прочитать файл B сразу после чтения файла A.
Что, если мы захотим продолжить чтение файла C после файла B? Для этого необходимо продолжать вкладывать обратные вызовы:
fs.readFile('./A.txt', (err, data) => {//First callback if (err) { console.log(err.message) возвращаться } console.log('Читать файл A: ' + data.toString()) fs.readFile('./B.txt', (err, data) => {//Второй обратный вызов if (err) { console.log(err.message) возвращаться } console.log("Читать файл B: " + data.toString()) fs.readFile('./C.txt',(err,data)=>{//Третий обратный вызов... }) }) })
Другими словами, если мы хотим выполнить несколько асинхронных операций последовательно, нам нужно несколько уровней вложенных обратных вызовов. Это эффективно, когда количество слоев невелико, но когда времени вложения слишком много, возникнут некоторые проблемы.
Соглашения об обратном вызове
На самом деле стиль функций обратного вызова в fs.readFile
не является исключением, а является общим соглашением в JavaScript
. В будущем мы будем настраивать большое количество функций обратного вызова, и нам необходимо соблюдать это соглашение и сформировать хорошие привычки программирования.
Соглашение таково:
callback
зарезервирован для ошибки. При возникновении ошибки будет вызван callback(err)
.callback(null, result1, result2,...)
.Согласно приведенному выше соглашению, функция обратного вызова имеет две функции: обработку ошибок и получение результатов. Например, функция обратного вызова fs.readFile('...',(err,data)=>{})
следует этому соглашению.
Если не копать глубже, асинхронная обработка методов на основе обратных вызовов кажется довольно идеальным способом справиться с этим. Проблема в том, что если у нас будет одно асинхронное поведение за другим, код будет выглядеть так:
fs.readFile('./a.txt',(err,data)=>{ если (ошибка) { console.log(err.message) возвращаться } //Чтение результата операции fs.readFile('./b.txt',(err,data)=>{ если (ошибка) { console.log(err.message) возвращаться } //Чтение результата операции fs.readFile('./c.txt',(err,data)=>{ если (ошибка) { console.log(err.message) возвращаться } //Чтение результата операции fs.readFile('./d.txt',(err,data)=>{ если (ошибка) { console.log(err.message) возвращаться } ... }) }) }) })
Содержимое выполнения приведенного выше кода:
По мере увеличения количества вызовов уровень вложенности кода становится все глубже и глубже, включая все больше и больше условных операторов, что приводит к запутанному коду, который постоянно имеет отступ вправо, что затрудняет его чтение и поддержку.
Мы называем это явление непрерывного роста вправо (отступ вправо) « адом обратного вызова » или « пирамидой гибели »!
fs.readFile('a.txt',(err,data)=>{ fs.readFile('b.txt',(err,data)=>{ fs.readFile('c.txt',(err,data)=>{ fs.readFile('d.txt',(err,data)=>{ fs.readFile('e.txt',(err,data)=>{ fs.readFile('f.txt',(err,data)=>{ fs.readFile('g.txt',(err,data)=>{ fs.readFile('h.txt',(err,data)=>{ ... /* Врата в ад ===> */ }) }) }) }) }) }) }) })
Хотя приведенный выше код выглядит вполне обычным, это просто идеальная ситуация, например. Обычно в бизнес-логике присутствует большое количество условных операторов, операций обработки данных и прочего кода, что нарушает текущий красивый порядок и делает код. сложно поддерживать.
К счастью, JavaScript
предоставляет нам множество решений, и Promise
— лучшее из них.