Разработка сайтов в Белозерском, ДНР. Как получить данные из стороннего API с помощью Deno

В этой статье мы рассмотрим Deno, относительно новый инструмент, созданный как конкурент/замена Node.js, который предлагает более безопасную среду и поставляется с поддержкой TypeScript из коробки.

Мы будем использовать Deno для создания инструмента командной строки для выполнения запросов к стороннему API — API Star Wars — и посмотрим, какие функции предоставляет Deno, чем он отличается от Node и как с ним работать.

Deno — это более самоуверенная среда выполнения, написанная на TypeScript, включающая собственный форматировщик кода (deno fmt) и использующая модули ES — без каких-либо requireоператоров CommonJS. По умолчанию он также чрезвычайно безопасен: вы должны явно разрешить своему коду делать сетевые запросы или читать файлы с дисков, что Node позволяет программам делать по умолчанию. В этой статье мы рассмотрим установку Deno, настройку нашей среды и создание простого приложения командной строки для выполнения запросов API.

Как всегда, вы можете найти код, сопровождающий эту статью, на GitHub.

Установка Дено

Полные инструкции можно найти на веб- сайте Deno. Если вы используете macOS или Linux, вы можете скопировать эту команду в свой терминал:

curl -fsSL https://deno.land/x/install/install.sh | sh

Вам также необходимо добавить каталог установки в ваш файл $PATH.

Не беспокойтесь, если вы работаете в Windows, так как вы можете установить Deno с помощью менеджеров пакетов, таких как Chocolatey:

choco install deno

Если Chocolately не для вас, deno_install перечисляет различные методы установки, поэтому выберите тот, который подходит вам лучше всего.

Вы можете проверить, установлен ли Deno, выполнив следующую команду:

deno -V

Это должно вывести версию Deno. На момент написания последней версией была 1.7.5, которую я и использую.

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

Обратите внимание, что если вы используете VS Code, по умолчанию плагин Deno не включается при загрузке проекта. Вы должны создать.vscode/settings.jsonфайл в своем репозитории и добавить следующее, чтобы включить плагин:

{

«deno.enable»: true

}

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

Написание нашего первого сценария

Давайте удостоверимся, что Deno запущен и работает. Создайте index.tsи поместите внутрь следующее:

console.log («hello world!») ;

Мы можем запустить это с помощью deno run index.ts:

$ deno run index.ts

Check file: ///home/jack/git/deno-star-wars-api/index.ts

hello world

Обратите внимание, что мы можем увидеть ошибку TypeScript в нашем редакторе:

'index.ts’ cannot be compiled under '—isolatedModules’

because it is considered a global script file. Add an import,

export, or an empty 'export {}' statement

to make it a module.ts (1208)

Эта ошибка возникает из-за того, что TypeScript не знает, что этот файл будет использовать импорт ES-модуля. Это будет скоро, потому что мы собираемся добавить импорт, а пока, если мы хотим удалить ошибку, мы можем добавить пустой exportоператор в конец скрипта:

export {}

Это убедит компилятор TypeScript, что мы используем модули ES, и избавится от ошибки. Я не буду включать это в какие-либо примеры кода в сообщении блога, но это ничего не изменит, если мы добавим его, кроме удаления шума TypeScript.

Получение в Дено

Deno реализует поддержку того же Fetch API, который мы привыкли использовать в браузере. Он встроен в Deno, а это значит, что нет необходимости устанавливать или настраивать пакет. Давайте посмотрим, как это работает, сделав наш первый запрос к API, который мы будем здесь использовать, Star Wars API (или SWAPI).

Сделав запрос https://swapi.dev/api/people/1/, мы вернем все данные, которые нам нужны для Люка Скайуокера. Давайте обновим наш index.tsфайл, чтобы сделать этот запрос. Обновите index.ts, чтобы выглядеть так:

const json = fetch («https: //swapi.dev/api/people/1») ;

json.then ((response) => {

return response.json () ;

}).then ((data) => {

console.log (data) ;

}) ;

Попробуйте запустить это в своем терминале с помощью deno run:

$ deno run index.ts

Check file: ///home/jack/git/deno-star-wars-api/index.ts

error: Uncaught (in promise) PermissionDenied: network access to «swapi.dev», run again with the —allow-net flag

throw new ErrorClass (res.err.message) ;

Deno по умолчанию безопасен, а это означает, что сценариям требуется разрешение на все, что можно считать опасным, например на чтение/запись в файловую систему и выполнение сетевых запросов. Мы должны предоставить разрешения сценариям Deno при их запуске, чтобы позволить им выполнять такие действия. Мы можем включить наш с —allow-netфлагом:

$ deno run —allow-net index.ts

Check file: ///home/jack/git/deno-star-wars-api/index.ts

{

name: «Luke Skywalker»,

... (data snipped to save space)...

}

Но этот флаг дал скрипту разрешение на доступ к любому URL-адресу. Мы можем быть немного более явными и разрешить нашему сценарию доступ только к URL-адресам, которые мы добавляем в белый список:

$ deno run —allow-net=swapi.dev index.ts

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

$ deno run —allow-net=swapi.dev index.ts

Мы также можем написать этот скрипт немного по-другому, используя await верхнего уровня, что позволяет нам использовать awaitключевое слово, а не иметь дело с промисами:

const response = await fetch («https: //swapi.dev/api/people/1/») ;

const data = await response.json () ;

console.log (data) ;

Это стиль, который я предпочитаю и буду использовать для этой статьи, но если вы предпочитаете придерживаться обещаний, не стесняйтесь.

Установка сторонних зависимостей

Теперь, когда мы можем делать запросы к Star Wars API, давайте начнем думать о том, как мы хотим разрешить нашим пользователям использовать этот API. Мы предоставим флаги командной строки, чтобы они могли указать, какой ресурс запрашивать (например, людей, фильмы или планеты), и запрос для их фильтрации. Таким образом, вызов нашего инструмента командной строки может выглядеть так:

$ deno run —allow-net=swapi.dev index.ts —resource=people —query=luke

Мы могли бы разобрать эти дополнительные аргументы командной строки вручную или использовать стороннюю библиотеку. В Node.js лучшим решением для этого является Yargs, а Yargs также поддерживает Deno, поэтому мы можем использовать Yargs для разбора и работы с флагами командной строки, которые мы хотим поддерживать.

Однако для Deno нет менеджера пакетов. Мы не создаем package.jsonи не устанавливаем зависимость. Вместо этого мы импортируем из URL-адресов. Лучшим источником пакетов Deno является репозиторий пакетов Deno, где вы можете найти нужный пакет. Большинство популярных пакетов npm теперь также поддерживают Deno, поэтому обычно там есть хороший выбор и высокая вероятность того, что вы найдете то, что вам нужно.

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

Обратите внимание на эту строку из приведенного выше вывода:

Warning Implicitly using latest version (v16.2.0-deno)

Это Deno сообщает нам, что мы не указали конкретную версию при импорте Yargs, поэтому он просто загрузил последнюю версию. Это, вероятно, хорошо для быстрых побочных проектов, но обычно рекомендуется привязывать наш импорт к той версии, которую мы хотели бы использовать. Мы можем сделать это, обновив URL:

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

дено фмт

Небольшое отступление, прежде чем мы продолжим создание нашего инструмента командной строки. Deno поставляется со встроенным средством форматирования, deno fmtкоторое автоматически форматирует код в соответствии со стилем. Думайте об этом как о Prettier, но специально для Deno и встроенном. Это еще одна причина, по которой меня тянет к Deno; Мне нравятся инструменты, которые предоставляют все это сразу, без необходимости что-либо настраивать.

Мы можем запустить средство форматирования локально следующим образом:

$ deno fmt

Это отформатирует все файлы JS и TS в текущем каталоге, или мы можем указать имя файла для форматирования:

$ deno fmt index.ts

Или, если у нас есть расширение VS Code, мы можем вместо этого перейти в.vscode/settings.json, где мы ранее включили плагин Deno, и добавить эти две строки:

{

«deno.enable»: true,

«editor.formatOnSave»: true,

«editor.defaultFormatter»: «denoland.vscode-deno»

}

Это настраивает VS Code для автоматического запуска deno fmtпри сохранении файла. Идеальный!

Использование яргов

Я не буду вдаваться в подробности о Yargs (вы можете прочитать документацию, если хотите ознакомиться со всеми его возможностями), но вот как мы объявляем, что хотим принимать два аргумента командной строки. которые требуются: —resourceи —query:

const userArguments: {

query: string;

resource: «films» | «people» | «planets»;

} = yargs (Deno.args)

.describe («resource», «the type of resource from SWAPI to query for»)

.choices («resource», [«people», «films», «planets»])

.describe («query», «the search term to query the SWAPI for»)

.demandOption ([«resource», «query»])

.argv;

console.log (userArguments) ;

Примечание: теперь, когда у нас есть importоператор, нам больше не нужно export {}заглушать эту ошибку TypeScript.

К сожалению, на момент написания статьи TypeScript, похоже, не поддерживает все определения типов: тип возвращаемого значения yargs (Deno.args) установлен на {}, так что давайте немного улучшим его. Мы можем определить наш собственный интерфейс TypeScript, который охватывает все части API Yargs, на которые мы полагаемся:

interface Yargs {

describe: (param: string, description: string) => Yargs;

choices: (param: string, options: string[]) => Yargs;

demandOption: (required: string[]) => Yargs;

argv: ArgvReturnType;

}

Здесь я объявляю функции, которые мы используем, и что они возвращают один и тот же интерфейс Yargs (это то, что позволяет нам вызывать цепочки). Я также беру универсальный тип, ArgvReturnTypeкоторый обозначает структуру аргументов, которые мы получаем после того, как Yargs их обработает. Это означает, что я могу объявить UserArgumentsтип и привести yargs (Deno.argv) к нему результат:

interface Yargs {

describe: (param: string, description: string) => Yargs;

choices: (param: string, options: string[]) => Yargs;

demandOption: (required: string[]) => Yargs;

argv: ArgvReturnType;

}

interface UserArguments {

query: string;

resource: «films» | «people» | «planets»;

}

const userArguments = (yargs (Deno.args) as Yargs)

.describe («resource», «the type of resource from SWAPI to query for»)

.choices («resource», [«people», «films», «planets»])

.describe («query», «the search term to query the SWAPI for»)

.demandOption ([«resource», «query»])

.argv;

Я уверен, что в будущем Yargs может предоставить эти типы из коробки, поэтому стоит проверить, используете ли вы более новую версию Yargs, чем 16.2.0.

Запросы к Star Wars API

Теперь, когда у нас есть метод приема пользовательского ввода, давайте напишем функцию, которая принимает то, что было введено, и правильно запрашивает Star Wars API:

async function queryStarWarsAPI (

resource: «films» | «people» | «planets»,

query: string,

): Promise<{

count: number;

results: object[];

}> {

const response = await fetch (url) ;

const data = await response.json () ;

return data;

}

Мы возьмем два аргумента: ресурс для поиска и сам поисковый запрос. Результат, возвращаемый Star Wars API, будет возвращать объект, включающий count (количество результатов) и resultsмассив, который представляет собой массив всех соответствующих ресурсов из нашего запроса API. Мы рассмотрим улучшение безопасности типов позже в этой статье, но сейчас я решил objectначать. Это не лучший тип для использования, так как он очень либерален, но иногда я предпочитаю, чтобы что-то работало, а затем улучшал типы позже.

Теперь у нас есть эта функция, мы можем взять аргументы, проанализированные Yargs, и получить некоторые данные!

const result = await queryStarWarsAPI (

userArguments.resource,

userArguments.query,

) ;

console.log (`${result.count} results`) ;

Теперь давайте запустим это:

$ deno run —allow-net=swapi.dev index.ts —resource films —query phantom

1 results

Мы видим, что получили один результат (скоро мы поработаем над неправильным множественным числом!). Давайте немного поработаем, чтобы получить более приятный вывод в зависимости от ресурса, который искал пользователь. Во-первых, я собираюсь немного поработать над TypeScript, чтобы улучшить возвращаемый тип, чтобы мы получили лучшую поддержку TypeScript в нашем редакторе.

Первое, что нужно сделать, это создать новый тип, представляющий ресурсы, которые мы разрешаем запрашивать у пользователя:

type StarWarsResource = «films» | «people» | «planets»;

Затем мы можем использовать этот тип, а не дублировать его, сначала, когда мы передаем его в Yargs, а второй раз, когда мы определяем queryStarWarsAPIфункцию:

interface UserArguments {

query: string;

resource: StarWarsResource;

}

//...

async function queryStarWarsAPI (

resource: StarWarsResource,

query: string,

): Promise<{

count: number;

results: object[];

}> {... }

Далее давайте взглянем на Star Wars API и создадим интерфейсы, представляющие то, что мы получим взамен на различные ресурсы. Эти типы не являются исчерпывающими (API возвращает больше). Я только что выбрал несколько элементов для каждого ресурса:

interface Person {

name: string;

films: string[];

height: string;

mass: string;

homeworld: string;

}

interface Film {

title: string;

episode_id: number;

director: string;

release_date: string;

}

interface Planet {

name: string;

terrain: string;

population: string;

}

Когда у нас есть эти типы, мы можем создать функцию для обработки результатов для каждого типа, а затем вызвать ее. Мы можем использовать приведение типов, чтобы сообщить TypeScript, что result.results (которое он считает object[]) на самом деле является одним из наших типов интерфейса:

console.log (`${result.count} results`) ;

switch (userArguments.resource) {

case «films»: {

logFilms (result.results as Film[]) ;

break;

}

case «people»: {

logPeople (result.results as Person[]) ;

break;

}

case «planets»: {

logPlanets (result.results as Planet[]) ;

break;

}

}

function logFilms (films: Film[]): void {... }

function logPeople (people: Person[]): void {... }

function logPlanets (planets: Planet[]): void {... }

Как только мы заполним эти функции небольшим количеством журналов, наш инструмент CLI будет готов!

function logFilms (films: Film[]): void {

films.forEach ((film) => {

console.log (film.title) ;

console.log (`=> Directed by ${film.director}`) ;

console.log (`=> Released on ${film.release_date}`) ;

}) ;

}

function logPeople (people: Person[]): void {

people.forEach ((person) => {

console.log (person.name) ;

console.log (`=> Height: ${person.height}`) ;

console.log (`=> Mass: ${person.mass}`) ;

}) ;

}

function logPlanets (planets: Planet[]): void {

planets.forEach ((planet) => {

console.log (planet.name) ;

console.log (`=> Terrain: ${planet.terrain}`) ;

console.log (`=> Population: ${planet.population}`) ;

}) ;

}

Давайте, наконец, исправим тот факт, что он выводит 1 results, а не 1 result:

function pluralise (singular: string, plural: string, count: number): string {

return `${count} ${count === 1? singular: plural}`;

}

console.log (pluralise («result», «results», result.count));

И теперь наш вывод CLI выглядит хорошо!

$ deno run —allow-net=swapi.dev index.ts —resource planets —query tat

1 result

Tatooine

=> Terrain: desert

=> Population: 200000

уборка

Прямо сейчас весь наш код представляет собой один большой index.tsфайл. Давайте создадим api.tsфайл и переместим в него большую часть логики API.

Не забудьте добавить exportв начало всех типов, интерфейсов и функций в этом файле, так как нам нужно будет импортировать их в index.ts:

// api.ts

export type StarWarsResource = «films» | «people» | «planets»;

export interface Person {

name: string;

films: string[];

height: string;

mass: string;

homeworld: string;

}

export interface Film {

title: string;

episode_id: number;

director: string;

release_date: string;

}

export interface Planet {

name: string;

terrain: string;

population: string;

}

export async function queryStarWarsAPI (

resource: StarWarsResource,

query: string,

): Promise<{

count: number;

results: object[];

}> {

const url = `https: //swapi.dev/api/${resource}/? search=${query}`;

const response = await fetch (url) ;

const data = await response.json () ;

return data;

}

И тогда мы можем импортировать их из index.ts:

import {

Film,

Person,

Planet,

queryStarWarsAPI,

StarWarsResource,

} from «. /api.ts»

Теперь наш index.tsвыглядит намного чище, и мы перенесли все детали API в отдельный модуль.

Распространение

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

Мы можем использовать deno bundleдля объединения всего нашего кода в один файл JavaScript со всеми установленными зависимостями. Таким образом, совместное использование сценария — это случай совместного использования одного файла:

$ deno bundle index.ts out.js

И мы можем передать этот скрипт deno.run, как и раньше. Разница теперь в том, что Deno не нужно выполнять проверку типов или устанавливать какие-либо зависимости, потому что все это было сделано out.jsза нас. Это означает, что запуск такого связанного скрипта, скорее всего, будет быстрее, чем запуск из исходного кода TypeScript:

$ deno run —allow-net=swapi.dev out.js —resource films —query phantom

1 result

The Phantom Menace

=> Directed by George Lucas

Другой вариант, который у нас есть, — создать один исполняемый файл, используя расширение deno compile. Обратите внимание, что на момент написания это считалось экспериментальным, поэтому действуйте осторожно, но я хочу включить это, так как ожидаю, что в будущем оно станет стабильным и более распространенным.

Мы можем deno compile —unstable —allow-net=swapi.dev index.tsпопросить Deno создать для нас автономный исполняемый файл. Флаг —unstableнеобходим, поскольку эта функция является экспериментальной, хотя в будущем ее не должно быть. Что хорошо в этом, так это то, что мы передаем флаги безопасности во время компиляции — в нашем случае разрешая доступ к Star Wars API. Это означает, что если мы дадим этот исполняемый файл пользователю, ему не нужно будет знать о настройке флагов:

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

Вывод

В этой статье, создав инструмент CLI, мы узнали, как использовать Deno для извлечения данных из стороннего API и отображения результатов. Мы увидели, как Deno реализует поддержку того же Fetch API, который мы привыкли использовать в браузере, как fetchон встроен в стандартную библиотеку Deno и как мы можем использовать awaitего на верхнем уровне нашей программы без необходимости оборачивать все в IFFE.

Надеюсь, вы согласитесь со мной, что в Deno есть за что любить. Он предоставляет очень продуктивную среду из коробки, в комплекте с TypeScript и средством форматирования. Замечательно не иметь накладных расходов на менеджер пакетов, особенно при написании небольших вспомогательных инструментов, а возможность компилировать в один исполняемый файл означает, что делиться этими инструментами с коллегами и друзьями очень просто.

Делитесь нашими материалами с друзьями!

 

 

Заказать разработку сайта