Async / await - это новый синтаксис, поставляемый с es7, который позволяет синхронно писать асинхронный код. В приведенном ниже примере показано, как новый синтаксис сравнивается с простым использованием обещаний.
// with async fns // Note: we are using a promisified version of request and readFile async function fetchAndReadAsync(url, dir) { var data = await requestPromise(url); var fromFiles = await readFilePromise(dir, ‘utf-8’) return [data.body, fromFiles]; } async function run() { var fromAsyncFunctions = await requestAndWriteAsync(“/foo.txt”, “https://www.foo.com/bar") return valsFromAsyncFunctions; } run() .then(success => console.log(success)) .catch(err => console.log(err.message, err.stack)); // with promises function fetchAndReadPromise (url, dir) { var dataBody; return requestPromise(url).then( ( data )=> { dataBody = data.body; return readFilePromise(dir, ‘utf-8’); }).then( ( fromFiles ) => { return [dataBody, fromFiles]; }) } function run() { var fromPromises; return run(“/foo.txt”, “https://www.foo.com/bar").then((data) => { fromPromises = data; return fromPromises; }) } run() .then(success => console.log(success)) .catch(err => console.log(err.message, err.stack));;
Некоторые могут возразить, что синтаксис async / await устраняет необходимость понимания обещаний, что в некоторой степени верно, поскольку он выполняет достойную работу по абстрагированию реализации Promise.
Однако я считаю, что понимание Promises позволяет использовать мощную форму асинхронного функционального программирования с async / await, которую вы можете получить только с помощью Javascript Promises.
Удивительный подъемный механизм с async / await
Интересно отметить, что, хотя синтаксис async / await используется для асинхронных функций, сама функция async выполняется синхронно и возвращает обещание.
var request = require('request'); var requestPromise = util.promisify(request); async function fetchAsync(url) { var data = await requestPromise(url); return data; } var pendingPromise = fetchAsync(‘https://foo.com'); console.log(pendingPromise) // Promise<…Pending>
Возвращаемое значение async fn не является возвращаемой переменной «data», а «ожидающим обещанием», которое после разрешения передаст переменную «data» в свойство функции «then». Таким образом, мы могли получить доступ к таким данным.
fetchAsync(‘https://foo.com') .then(function(data) { console.log(data) // {body: [object Object]} })
Ключевым моментом здесь является то, что fetchAsync возвращает ожидающее обещание, что означает, что возвращаемое значение функции fetchAsync также может ожидаться.
async function run() { var data = await fetchAsync(‘https://foo.com'); return data; }
Это позволяет нам использовать парадигму FP, называемую «лифтинг». Лифтинг - это преобразование данных из одной формы в другую. Например, Array.prototype.map берет массив и переводит его в другую форму массива. (Интересный факт функционального программирования: карта определяется как функция, выполняющая подъем)
Исправление: как упоминалось в комментариях, Array.prototype.map не поднимает один массив на другой, а, скорее, поднимает функцию, которая предназначена для того, чтобы одна запись могла быть применена к массиву этой записи.
Вот пример подъема с использованием нашей функции «fetchAsync» сверху.
var request = require('request'); var fs = require('fs'); var requestPromise = util.promisify(request); var writeFilePromise = util.promisify(fs.writeFile); // mapToWriteFile will lift our pending fetch promise to a // pending fetch and write promise async mapToWriteFile(pendingFetch, dir) { var data = await pendingFetch; var written = await writeFilePromise(data, dir); return written; } async function run() { var pendingFetch = fetchAsync('https://foo.com'); var pendingFetchAndWrite = mapToWriteFile(pendingFetch, '/foo.txt'); var success = await pendingFetchAndWrite; // fs.writeFile does not pass a value into it’s ‘then’ function // property, but in case that it did the return value would be // thenable with the success message. return success; } run() .then(success=> console.log(success)) .catch(err => console.log(err.message, err.stack));
В этом примере мы подняли requestPromise с помощью функции mapToWriteFile, получив ожидающее обещание, которое будет разрешено после завершения как выборки данных, так и записи файла.
Это довольно хорошо интегрируется в систему функционального программирования Javascript, как мы видим на более сложном примере.
import urls from ‘./modules/urls’; // array of urls // the fetchAsync function is taken from the previous example async function run() { var pendingFetches = urls.map(fetchAsync); var pendingWrites = pendingFetches.map( pendingFetch => mapToWriteFile(pendingFetch, ‘/foo.txt’) ); var success = await Promise.all(pendingWrites); return success; } run() .then(success => console.log(success)) .catch(err => console.log(err.message, err.stack));
Удивительная гарантия неизменности
Интересно отметить, что асинхронные функции на самом деле не запускают наш асинхронный код; они только обещают, что код будет работать по мере поворота цикла событий.
Это означает, что асинхронная функция не может гарантировать отсутствие побочных эффектов в текущем стеке вызовов. Например:
async function run( mutableData ) { var data = await fetchAsync(‘https://foo.com'); //this change doesn’t go into effect in the current call stack mutableData.prop = data.body; } run();
Поэтому, когда мы передаем данные в асинхронную функцию, мы можем с уверенностью предположить, что они не будут видоизменяться при прохождении через стек вызовов.
Предостережение: код, написанный над первым ключевым словом «await», выполняется синхронно, поэтому гарантия действует до тех пор, пока кто-то избегает мутации ввода над первым ключевым словом «await» (что было бы необычно).
До сих пор это был мой опыт работы с async / await, есть другие утилиты функционального программирования, которые я не исследовал, такие как каррирование и композиция функций. Но до следующего раза ...