Deno Namespace APIs
Глобальное Deno пространство имён содержит API, которые не являются веб-стандартами, включая API для чтения файлов, открытия TCP-сокет, предоставления HTTP и выполнения подпроцессов и т. д.
Ниже мы выделим некоторые из самых важных API Deno.
Система файлов
Интерпретатор Deno поставляется с различными функциями для работы с файлами и каталогами. Вам потребуется использовать разрешения --allow-read и --allow-write, чтобы получить доступ к файловой системе.
Обратитесь к ссылкам ниже для получения примеров кода, как использовать функции файловой системы.
- Чтение файлов различными способами
- Чтение файлов потоками
- Чтение текстового файла (
Deno.readTextFile) - Запись текстового файла (
Deno.writeTextFile)
Сеть
Интерпретатор Deno поставляется с встроенными функциями для работы с подключениями к сетевым портам.
Обратитесь к ссылкам ниже для получения примеров кода для общих функций.
- Подключение к хосту и порту (
Deno.connect) - Объявление на локальном транспортном адресе (
Deno.listen)
Подпроцессы
Интерпретатор Deno поставляется с встроенными функциями для запуска подпроцессов.
Обратитесь к ссылкам ниже для примеров кода, как создать подпроцесс.
Ошибки
Интерпретатор Deno поставляется с 20 классами ошибок, которые могут быть подняты в ответ на ряд условий.
Вот некоторые примеры:
Deno.errors.NotFound;
Deno.errors.WriteZero;
Они могут быть использованы следующим образом:
try {
const file = await Deno.open("./some/file.txt");
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
console.error("the file was not found");
} else {
// otherwise re-throw
throw error;
}
}
HTTP-сервер
Deno имеет два API HTTP-серверов:
-
Deno.serve: нативный, высокоуровневый, поддерживает HTTP/1.1 и HTTP2, это предпочтительный API для написания HTTP-серверов в Deno. -
Deno.serveHttp: нативный, низкоуровневый, поддерживает HTTP/1.1 и HTTP2.
Для запуска HTTP-сервера на заданном порту используйте функцию Deno.serve. Эта функция принимает функцию-обработчик, которая будет вызываться для каждого входящего запроса и должна возвращать ответ (или промис, разрешающий ответ). Например:
Deno.serve((_req) => {
return new Response("Hello, World!");
});
По умолчанию Deno.serve будет слушать порт 8000, но это можно изменить, передав номер порта в пакете опций в качестве первого или второго аргумента.
Вы можете подробнее узнать о том, как использовать API HTTP-сервера.
Разрешения
Разрешения предоставляются из командной строки при запуске команды deno. Код пользователя часто предполагает свой собственный набор необходимых разрешений, но нет гарантии во время выполнения, что набор предоставленных разрешений будет совпадать с этим.
В некоторых случаях для обеспечения отказоустойчивости программы требуется способ взаимодействия с системой разрешений во время выполнения.
Дескрипторы разрешений
В командной строке разрешение на чтение для /foo/bar представлено как --allow-read=/foo/bar. В JavaScript во время выполнения оно представлено следующим образом:
const desc = { name: "read", path: "/foo/bar" } as const;
Другие примеры:
// Global write permission.
const desc1 = { name: "write" } as const;
// Write permission to `$PWD/foo/bar`.
const desc2 = { name: "write", path: "foo/bar" } as const;
// Global net permission.
const desc3 = { name: "net" } as const;
// Net permission to 127.0.0.1:8000.
const desc4 = { name: "net", host: "127.0.0.1:8000" } as const;
// High-resolution time permission.
const desc5 = { name: "hrtime" } as const;
См. PermissionDescriptor в справочнике API для получения дополнительной информации. Синхронные API-аналоги (например, Deno.permissions.querySync) существуют для всех описанных ниже API.
Запрос разрешений
Проверьте, предоставлено ли разрешение по описателю или нет.
// deno run --allow-read=/foo main.ts
const desc1 = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.query(desc1));
// PermissionStatus { state: "granted", partial: false }
const desc2 = { name: "read", path: "/foo/bar" } as const;
console.log(await Deno.permissions.query(desc2));
// PermissionStatus { state: "granted", partial: false }
const desc3 = { name: "read", path: "/bar" } as const;
console.log(await Deno.permissions.query(desc3));
// PermissionStatus { state: "prompt", partial: false }
Если флаг --deny-read был использован для ограничения некоторых путей к файлам, результат будет содержать partial: true, указывающее, что не всем подпутям предоставлены разрешения:
// deno run --allow-read=/foo --deny-read=/foo/bar main.ts
const desc1 = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.query(desc1));
// PermissionStatus { state: "granted", partial: true }
const desc2 = { name: "read", path: "/foo/bar" } as const;
console.log(await Deno.permissions.query(desc2));
// PermissionStatus { state: "denied", partial: false }
const desc3 = { name: "read", path: "/bar" } as const;
console.log(await Deno.permissions.query(desc3));
// PermissionStatus { state: "prompt", partial: false }
Состояния разрешений
Состояние разрешения может быть «предоставлено», «запрошено» или «запрещено». Разрешения, которые были предоставлены из командной строки, будут запрашиваться как { state: "granted" }. Те, которые не были предоставлены, запрашиваются как { state: "prompt" } по умолчанию, в то время как { state: "denied" } предназначены для тех, которые были явно запрещены. Это будет появляться в Запросе разрешений.
Сила разрешений
Интуитивное понимание результата второго запроса в Запросе разрешений заключается в том, что доступ на чтение был предоставлен для /foo, и /foo/bar находится внутри /foo, поэтому /foo/bar может быть прочитан. Это верно, если предоставленное из командной строки разрешение не является *частичным* запрошенным разрешениям (в результате использования флага --deny-*).
Мы также можем сказать, что desc1 *менее строгий, чем* desc2. Это означает, что для любого набора разрешений, предоставленных из командной строки:
- Если
desc1запрашивает{ state: "granted", partial: false }, то так же должно запрашиватьсяdesc2. - Если
desc2запрашивает{ state: "denied", partial: false }, то так же должно запрашиватьсяdesc1.
Дополнительные примеры:
const desc1 = { name: "write" } as const;
// is stronger than
const desc2 = { name: "write", path: "/foo" } as const;
const desc3 = { name: "net", host: "127.0.0.1" } as const;
// is stronger than
const desc4 = { name: "net", host: "127.0.0.1:8000" } as const;
Запрос разрешений
Запросите не предоставленное разрешение у пользователя через запрос в командной строке.
// deno run main.ts
const desc1 = { name: "read", path: "/foo" } as const;
const status1 = await Deno.permissions.request(desc1);
// ⚠️ Deno requests read access to "/foo". Grant? [y/n (y = yes allow, n = no deny)] y
console.log(status1);
// PermissionStatus { state: "granted", partial: false }
const desc2 = { name: "read", path: "/bar" } as const;
const status2 = await Deno.permissions.request(desc2);
// ⚠️ Deno requests read access to "/bar". Grant? [y/n (y = yes allow, n = no deny)] n
console.log(status2);
// PermissionStatus { state: "denied", partial: false }
Если текущее состояние разрешения равно «запрос», на терминале пользователя появится запрос, спрашивающий, хочет ли он предоставить запрос. Запрос на desc1 был предоставлен, поэтому его новое состояние возвращается, и выполнение будет продолжено так, как если бы --allow-read=/foo было указано в командной строке. Запрос на desc2 был отклонен, поэтому его состояние разрешения понижено с «запрос» до «отказ».
Если текущее состояние разрешения уже равно «предоставлено» или «запрещено», запрос будет работать как запрос и просто вернет текущее состояние. Это предотвращает запросы как для уже предоставленных, так и для ранее отклоненных запросов.
Отзыв разрешений
Понизить разрешение с «предоставлено» до «запрос».
// deno run --allow-read=/foo main.ts
const desc = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.revoke(desc));
// PermissionStatus { state: "prompt", partial: false }
Что происходит, когда вы пытаетесь отозвать разрешение, которое является *частичным* к одному, предоставленному из командной строки?
// deno run --allow-read=/foo main.ts
const desc = { name: "read", path: "/foo/bar" } as const;
console.log(await Deno.permissions.revoke(desc));
// PermissionStatus { state: "prompt", partial: false }
const cliDesc = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.revoke(cliDesc));
// PermissionStatus { state: "prompt", partial: false }
Предоставленное из командной строки разрешение, которое подразумевает отозванное разрешение, также было отозвано.
Чтобы понять это поведение, представьте, что Deno хранит внутренний набор *явно предоставленных дескрипторов разрешений*. Указание --allow-read=/foo,/bar в командной строке инициализирует этот набор следующим образом:
[
{ name: "read", path: "/foo" },
{ name: "read", path: "/bar" },
];
Предоставление запроса во время выполнения для { name: "write", path: "/foo" } обновляет набор следующим образом:
[
{ name: "read", path: "/foo" },
{ name: "read", path: "/bar" },
{ name: "write", path: "/foo" },
];
Алгоритм отзыва разрешений Deno работает путем удаления каждого элемента из этого набора, который *более строгий, чем* дескриптор разрешения аргумента.
Deno не допускает «фрагментированных» состояний разрешений, где некоторое сильное разрешение предоставляется с исключениями слабых разрешений, подразумеваемых им. Такая система окажется все более сложной и непредсказуемой по мере учета более широкого спектра вариантов использования и состояния "denied". Это выверенный компромисс между гранулярностью и безопасностью.
import.meta
Deno поддерживает ряд свойств и методов в API import.meta. Его можно использовать для получения информации о модуле, такой как URL модуля.
import.meta.url
Возвращает URL текущего модуля.
console.log(import.meta.url);
$ deno run main.ts
file:///dev/main.ts
$ deno run https:/example.com/main.ts
https://example.com/main.ts
import.meta.main
Возвращает, является ли текущий модуль точкой входа в вашу программу.
import "./other.ts";
console.log(`Is ${import.meta.url} the main module?`, import.meta.main);
console.log(`Is ${import.meta.url} the main module?`, import.meta.main);
$ deno run main.ts
Is file:///dev/other.ts the main module? false
Is file:///dev/main.ts the main module? true
import.meta.filename
Это свойство доступно только для локальных модулей (модули, имеющие file:///... спецификатор) и возвращает undefined для удалённых модулей.
Возвращает полное разрешённый путь к текущему модулю. Значение содержит разделители путей, специфичные для ОС.
console.log(import.meta.filename);
В Unix:
$ deno run main.ts
/dev/main.ts
$ deno run https://example.com/main.ts
undefined
В Windows:
$ deno run main.ts
C:\dev\main.ts
$ deno run https://example.com/main.ts
undefined
import.meta.dirname
Это свойство доступно только для локальных модулей (модули, имеющие file:///... спецификатор) и возвращает undefined для удалённых модулей.
Возвращает полное разрешённый путь к каталогу, содержащему текущий модуль. Значение содержит разделители путей, специфичные для ОС.
console.log(import.meta.dirname);
В Unix:
$ deno run main.ts
/dev/
$ deno run https://example.com/main.ts
undefined
В Windows:
$ deno run main.ts
C:\dev\
$ deno run https://example.com/main.ts
undefined
import.meta.resolve
Разрешение спецификаторов относительно текущего модуля.
const worker = new Worker(import.meta.resolve("./worker.ts"));
API import.meta.resolve учитывает текущую применённую карту импорта, что даёт вам возможность разрешать «голые» спецификаторы.
При такой загруженной карте импорта...
{
"imports": {
"fresh": "https://deno.land/x/fresh@1.0.1/dev.ts"
}
}
...вы можете теперь разрешить:
console.log(import.meta.resolve("fresh"));
$ deno run resolve.js
https://deno.land/x/fresh@1.0.1/dev.ts
FFI
API FFI (интерфейс внешних функций) позволяет пользователям вызывать библиотеки, написанные на родных языках, поддерживающих C ABIs (C/C++, Rust, Zig, V и т. д.), используя Deno.dlopen.
Вот пример, демонстрирующий, как вызвать функцию Rust из Deno:
// add.rs
#[no_mangle]
pub extern "C" fn add(a: isize, b: isize) -> isize {
a + b
}
Компилируйте его в динамическую библиотеку C (libadd.so в Linux):
rustc --crate-type cdylib add.rs
В C вы можете написать это так:
// add.c
int add(int a, int b) {
return a + b;
}
И скомпилировать его:
// unix
cc -c -o add.o add.c
cc -shared -W -o libadd.so add.o
// Windows
cl /LD add.c /link /EXPORT:add
Вызов библиотеки из Deno:
// ffi.ts
// Determine library extension based on
// your OS.
let libSuffix = "";
switch (Deno.build.os) {
case "windows":
libSuffix = "dll";
break;
case "darwin":
libSuffix = "dylib";
break;
default:
libSuffix = "so";
break;
}
const libName = `./libadd.${libSuffix}`;
// Open library and define exported symbols
const dylib = Deno.dlopen(
libName,
{
"add": { parameters: ["isize", "isize"], result: "isize" },
} as const,
);
// Call the symbol `add`
const result = dylib.symbols.add(35, 34); // 69
console.log(`Result from external addition of 35 and 34: ${result}`);
Запустите с флагом --allow-ffi и флагом --unstable:
deno run --allow-ffi --unstable ffi.ts
Асинхронное FFI
Существует много случаев, когда пользователям может потребоваться запускать ресурсоемкие функции FFI в фоновом режиме, не блокируя другие задачи в главном потоке.
Начиная с Deno 1.15, символы могут быть помечены nonblocking в Deno.dlopen. Эти вызовы функций будут выполняться в выделенном блокирующем потоке и вернут Promise, разрешающее желаемое result.
Пример выполнения дорогостоящих вызовов FFI с помощью Deno:
// sleep.c
#ifdef _WIN32
#include <Windows.h>
#else
#include <time.h>
#endif
int sleep(unsigned int ms) {
#ifdef _WIN32
Sleep(ms);
#else
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&ts, NULL);
#endif
}
Вызов из Deno:
// nonblocking_ffi.ts
const library = Deno.dlopen(
"./sleep.so",
{
sleep: {
parameters: ["usize"],
result: "void",
nonblocking: true,
},
} as const,
);
library.symbols.sleep(500).then(() => console.log("After"));
console.log("Before");
Результат:
$ deno run --allow-ffi --unstable unblocking_ffi.ts
Before
After
Обратные вызовы
API Deno FFI поддерживает создание обратных вызовов C из функций JavaScript для вызова обратных вызовов в Deno из динамических библиотек. Пример создания и использования обратных вызовов приведен ниже:
// callback_ffi.ts
const library = Deno.dlopen(
"./callback.so",
{
set_status_callback: {
parameters: ["function"],
result: "void",
},
start_long_operation: {
parameters: [],
result: "void",
},
check_status: {
parameters: [],
result: "void",
},
} as const,
);
const callback = new Deno.UnsafeCallback(
{
parameters: ["u8"],
result: "void",
} as const,
(success: number) => {},
);
// Pass the callback pointer to dynamic library
library.symbols.set_status_callback(callback.pointer);
// Start some long operation that does not block the thread
library.symbols.start_long_operation();
// Later, trigger the library to check if the operation is done.
// If it is, this call will trigger the callback.
library.symbols.check_status();
Если функция обратного вызова UnsafeCallback вызывает ошибку, ошибка будет передана функции, которая вызвала обратный вызов (выше это check_status()) и может быть перехвачена там. Если обратный вызов, возвращающий значение, вызывает ошибку, Deno вернет 0 (нулевой указатель для указателей) как результат.
UnsafeCallback по умолчанию не освобождается, так как это может вызвать ошибки использования после освобождения. Чтобы правильно утилизировать UnsafeCallback, необходимо вызвать метод close().
const callback = new Deno.UnsafeCallback(
{ parameters: [], result: "void" } as const,
() => {},
);
// After callback is no longer needed
callback.close();
// It is no longer safe to pass the callback as a parameter.
Также возможно, чтобы родные библиотеки настраивали обработчики прерываний и непосредственно вызывали обратный вызов. Однако это не рекомендуется и может привести к непредвиденным побочным эффектам и неопределенному поведению. В идеале любые обработчики прерываний должны устанавливать только флаг, который позже можно опросить аналогично тому, как используется check_status() выше.
Поддерживаемые типы
Вот список типов, в настоящее время поддерживаемых API Deno FFI.
| Тип FFI | Deno | C | Rust |
|---|---|---|---|
i8 | number |
char / signed char
| i8 |
u8 | number | unsigned char | u8 |
i16 | number | short int | i16 |
u16 | number | unsigned short int | u16 |
i32 | number |
int / signed int
| i32 |
u32 | number | unsigned int | u32 |
i64 | number | bigint | long long int | i64 |
u64 | number | bigint | unsigned long long int | u64 |
usize | number | bigint | size_t | usize |
isize | number | bigint | size_t | isize |
f32 | number | bigint | float | f32 |
f64 | number | bigint | double | f64 |
void[1] | undefined | void | () |
pointer | {} | null | void * | *mut c_void |
buffer[2] | TypedArray | null | uint8_t * | *mut u8 |
function[3] | {} | null | void (*fun)() | Option<extern "C" fn()> |
{ struct: [...] }[4] | TypedArray | struct MyStruct | MyStruct |
Начиная с Deno 1.25, тип pointer был разделен на тип pointer и тип buffer для обеспечения оптимизации для типизированных массивов, а начиная с Deno 1.31, JavaScript-представление pointer стало объектом неявного указателя или null для нулевых указателей.
- [1] Тип
voidможет использоваться только в качестве типа результата. - [2] Тип
bufferпринимает TypedArrays в качестве параметра, но всегда возвращает объект указателя илиnull, когда используется в качестве типа результата, как типpointer. - [3] Тип
functionработает точно так же, как типpointerкак параметр и тип результата. - [4] Тип
structпредназначен для передачи и возврата структур C по значению (копия). Массивstructдолжен перечислить тип каждого поля структуры в порядке. Структуры автоматически заполняются: упакованные структуры можно определить, используя необходимое количество полейu8для избежания заполнения. Поддерживаются только TypedArrays, и структуры всегда возвращаются какUint8Array.
deno_bindgen
deno_bindgen — официальный инструмент для упрощения генерации связующего кода для библиотек Deno FFI, написанных на Rust.
Он аналогичен wasm-bindgen в экосистеме Rust Wasm.
Вот пример его использования:
// mul.rs
use deno_bindgen::deno_bindgen;
#[deno_bindgen]
struct Input {
a: i32,
b: i32,
}
#[deno_bindgen]
fn mul(input: Input) -> i32 {
input.a * input.b
}
Запустите deno_bindgen для генерации связей. Теперь вы можете напрямую импортировать их в Deno:
// mul.ts
import { mul } from "./bindings/bindings.ts";
mul({ a: 10, b: 2 }); // 20
Любые проблемы, связанные с deno_bindgen, следует сообщать по адресу https://github.com/denoland/deno_bindgen/issues
Жизненный цикл программы
Deno поддерживает события жизненного цикла, совместимые с браузером:
-
load: срабатывает, когда вся страница загружена, включая все зависимые ресурсы, такие как таблицы стилей и изображения. -
beforeunload: срабатывает, когда цикл событий больше не имеет работы и собирается завершиться. Планирование дополнительной асинхронной работы (таких как таймеры или сетевые запросы) заставит программу продолжить работу. -
unload: срабатывает, когда документ или дочерний ресурс загружаются. -
unhandledrejection: срабатывает, когда обещание без обработчика отклонения отклоняется, то есть обещание без обработчика.catch()или второго аргумента.then(). -
rejectionhandled: срабатывает, когда обработчик.catch()добавляется к обещанию, которое уже отклонено. Это событие срабатывает только в том случае, если установлен обработчикunhandledrejectionслушатель, который предотвращает распространение события (что приведет к завершению программы с ошибкой).
Вы можете использовать эти события для предоставления кода подготовки и завершения в вашей программе.
Обработчики событий load могут быть асинхронными и будут ожидаться, это событие нельзя отменить. Обработчики событий beforeunload должны быть синхронными и могут быть отменены для продолжения работы программы. Обработчики событий unload должны быть синхронными и не могут быть отменены.
main.ts
import "./imported.ts";
const handler = (e: Event): void => {
console.log(`got ${e.type} event in event handler (main)`);
};
globalThis.addEventListener("load", handler);
globalThis.addEventListener("beforeunload", handler);
globalThis.addEventListener("unload", handler);
globalThis.onload = (e: Event): void => {
console.log(`got ${e.type} event in onload function (main)`);
};
globalThis.onbeforeunload = (e: Event): void => {
console.log(`got ${e.type} event in onbeforeunload function (main)`);
};
globalThis.onunload = (e: Event): void => {
console.log(`got ${e.type} event in onunload function (main)`);
};
console.log("log from main script");
const handler = (e: Event): void => {
console.log(`got ${e.type} event in event handler (imported)`);
};
globalThis.addEventListener("load", handler);
globalThis.addEventListener("beforeunload", handler);
globalThis.addEventListener("unload", handler);
globalThis.onload = (e: Event): void => {
console.log(`got ${e.type} event in onload function (imported)`);
};
globalThis.onbeforeunload = (e: Event): void => {
console.log(`got ${e.type} event in onbeforeunload function (imported)`);
};
globalThis.onunload = (e: Event): void => {
console.log(`got ${e.type} event in onunload function (imported)`);
};
console.log("log from imported script");
Несколько замечаний по этому примеру:
-
addEventListenerиonload/onunloadимеют префиксglobalThis, но вы также можете использоватьselfили вообще без префикса. Не рекомендуется использоватьwindowв качестве префикса. - Вы можете использовать
addEventListenerи/илиonload/onunloadдля определения обработчиков событий. Между ними существует существенная разница, давайте запустим пример:
$ deno run main.ts
log from imported script
log from main script
got load event in event handler (imported)
got load event in event handler (main)
got load event in onload function (main)
got onbeforeunload event in event handler (imported)
got onbeforeunload event in event handler (main)
got onbeforeunload event in onbeforeunload function (main)
got unload event in event handler (imported)
got unload event in event handler (main)
got unload event in onunload function (main)
Все обработчики, добавленные с помощью addEventListener, были выполнены, но onload, onbeforeunload и onunload, определенные в main.ts, перезаписали обработчики, определенные в imported.ts.
Другими словами, вы можете использовать addEventListener для регистрации нескольких обработчиков событий "load" или "unload", но будут выполнены только последние определенные обработчики событий onload, onbeforeunload, onunload . По этой причине предпочтительно использовать addEventListener при возможности.
beforeunload
// beforeunload.js
let count = 0;
console.log(count);
globalThis.addEventListener("beforeunload", (e) => {
console.log("About to exit...");
if (count < 4) {
e.preventDefault();
console.log("Scheduling more work...");
setTimeout(() => {
console.log(count);
}, 100);
}
count++;
});
globalThis.addEventListener("unload", (e) => {
console.log("Exiting");
});
count++;
console.log(count);
setTimeout(() => {
count++;
console.log(count);
}, 100);
При запуске этой программы будет выведено:
$ deno run beforeunload.js
0
1
2
About to exit...
Scheduling more work...
3
About to exit...
Scheduling more work...
4
About to exit...
Exiting
Событие unhandledrejection
Это событие срабатывает, когда промис, у которого нет обработчика отмены (reject), отклоняется, т. е. промис, у которого нет обработчика .catch() или второго аргумента .then().
// unhandledrejection.js
globalThis.addEventListener("unhandledrejection", (e) => {
console.log("unhandled rejection at:", e.promise, "reason:", e.reason);
e.preventDefault();
});
function Foo() {
this.bar = Promise.reject(new Error("bar not available"));
}
new Foo();
Promise.reject();
Запуск этой программы выведет:
$ deno run unhandledrejection.js
unhandled rejection at: Promise {
<rejected> Error: bar not available
at new Foo (file:///dev/unhandled_rejection.js:7:29)
at file:///dev/unhandled_rejection.js:10:1
} reason: Error: bar not available
at new Foo (file:///dev/unhandled_rejection.js:7:29)
at file:///dev/unhandled_rejection.js:10:1
unhandled rejection at: Promise { <rejected> undefined } reason: undefined
© 2018–2024 the Deno authors
Licensed under the MIT License.
https://docs.deno.com/runtime/reference/deno_namespace_apis