Как вернуть результат из асинхронного вызова?
2 февраля 2021 г. • ☕️ 4 мин.
Проблема
Такой код часто вводит в заблуждение разработчиков, не знакомых с понятием асинхронности
в языке JavaScript.
const fs = require("fs")
let file
fs.readFile("./file.txt", (err, result) => {
if (err) throw err
file = result
return result
})
console.log(file) // undefined - why?
Чтобы понять, почему на первый взгляд интуитивно написанный код не работает, как ожидается, нужно разобраться в разнице между синхронностью и асинхронностью.
В JavaScript есть возможность писать, как синхронный так и асинхронный код. Что это значит и в чем разница?
I/O
Когда мы хотим взаимодействовать с внешним окружением из нашей программы (прочитать файл с диска, запросить информацию по сети), мы используем понятие Ввод/Вывод (Input/Output)
или I/O
.
I/O
вызов может быть блокирующим (blocking)
или не блокирующим (non-blocking)
.
Блокирующий вызов
Блокирующий вызов
останавливает (блокирует) выполнение программы, до тех пор, пока мы не получим результат.
const fs = require("fs")
// программа ожидает, пока мы не прочитаем файл целиком
const file = fs.readFileSync("./file.txt")
console.log(file)
Не блокирующий вызов
Не блокирующий вызов
продолжает выполнение программы, не дожидаясь результата.
const fs = require("fs")
// программа продолжает выполнение
fs.readFile("./file.txt", (err, file) => { if (err) throw err
console.log(file)
})
// ... more code
Решение
Как получить резлуьтат из не блокирующего вызова
если программа продолжила выполнять другой код не дожидаясь результата? Попросить окружение вернуть нам результат, когда он будет готов.
Для этого мы передаем функцию обратного вызова Callback
в не блокирующий вызов
, например readFile
.
Что значит попросить окружение
?
Окружение это та среда, где мы запускаем программу, например Browser
или Node.js на сервере.
fs.readFile("./file.txt", callback) // (1)
function callback(err, file) {
if (err) throw err
console.log(file)
}
(1) Дословно, здесь мы говорим: Эй, Node.js, прочитай файл с именем "./file.txt"
и как будешь готов, вызови функцию callback
с результатом.
Последовательность действий
Как сделать несколько вызовов, один за другим?
Есть несколько решений:
Вложить один вызов, в callback
другого
const fs = require("fs")
fs.readFile("./file-with-cat.txt", (err, fileWithCat) => { if (err) throw err
fs.readFile("./file-with-dog.txt", (err, fileWithDog) => { if (err) throw err
console.log(fileWithCat.length + fileWithDog.length) // more code...
})
})
Использовать Promise
const fs = require('fs/promises')
fs.readFile("./file-with-cat.txt") .then(fileWithCat => {
console.log(fileWithCat)
return fs.readFile("./file-with-dog.txt") })
.then((fileWithDog) => {
console.log(fileWithCat) })
Использовать async/await
const fs = require('fs/promises')
async function main() {
const fileWithCat = await fs.readFile("./file-with-cat.txt") const fileWithDog = await fs.readFile("./file-with-dog.txt")}
main()
Заключение
Теперь ты должен понимать, как правильно писать и читать код с не блокирующими
вызовами I/O
.