1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-08-10 21:51:42 +02:00

use only let

This commit is contained in:
Sergey Konstantinov
2023-09-03 20:30:34 +03:00
parent 85097b4f9f
commit 84a7aa02f8
18 changed files with 100 additions and 100 deletions

View File

@@ -230,10 +230,10 @@ The condition “coffee might be prepared” would look like `has_beans && has_c
This advice contradicts the previous one, ironically. When developing APIs you frequently need to add a new optional field with a non-empty default value. For example:
```typescript
const orderParams = {
let orderParams = {
contactless_delivery: false
};
const order = api.createOrder(
let order = api.createOrder(
orderParams
);
```
@@ -241,7 +241,7 @@ const order = api.createOrder(
This new `contactless_delivery` option isn't required, but its default value is `true`. A question arises: how should developers discern the explicit intention to disable the option (`false`) from not knowing if it exists (the field isn't set)? They would have to write something like:
```typescript
const value = orderParams.contactless_delivery;
let value = orderParams.contactless_delivery;
if (Type(value) == 'Boolean' && value == false) {
}
@@ -254,10 +254,10 @@ If the protocol does not support resetting to default values as a first-class ci
**Better**
```typescript
const orderParams = {
let orderParams = {
/* <em> */force_contact_delivery: true/* </em> */
};
const order = api.createOrder(
let order = api.createOrder(
orderParams
);
```

View File

@@ -8,7 +8,7 @@ Let's proceed to the technical problems that API developers face. We begin with
3. The client requests the current state of the system and gets an empty response as the initial request still hasn't reached the server:
```typescript
const pendingOrders = await
let pendingOrders = await
api.getOngoingOrders(); // → []
```
@@ -34,12 +34,12 @@ try {
acquireLock(ORDER_CREATION);
// Get the list of current orders
// known to the system
const pendingOrders = await
let pendingOrders = await
api.getPendingOrders();
// If our order is absent,
// create it
if (pendingOrders.length == 0) {
const order = await api
let order = await api
.createOrder(…)
}
} catch (e) {
@@ -63,17 +63,17 @@ Rather unsurprisingly, this approach sees very rare use in distributed client-se
```typescript
// Retrieve the state
const orderState =
let orderState =
await api.getOrderState();
// The version is a part
// of the state of the resource
const version =
let version =
orderState.latestVersion;
// An order might only be created
// if the resource version hasn't
// changed since the last read
try {
const task = await api
let task = await api
.createOrder(version, …);
} catch (e) {
// If the version is wrong, i.e.,

View File

@@ -5,15 +5,15 @@ The approach described in the previous chapter is in fact a trade-off: the API p
```typescript
// Reading the state,
// possibly from a replica
const orderState =
let orderState =
await api.getOrderState();
const version =
let version =
orderState.latestVersion;
try {
// The request handler will
// read the actual version
// from the master data
const task = await api
let task = await api
.createOrder(version, );
} catch (e) {
@@ -28,10 +28,10 @@ Choosing weak consistency instead of a strict one, however, brings some disadvan
```typescript
// Creates an order
const api = await api
let api = await api
.createOrder()
// Returns a list of orders
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders(); // → []
// The list is empty
```
@@ -41,9 +41,9 @@ If strict consistency is not guaranteed, the second call might easily return an
An important pattern that helps in this situation is implementing the “read-your-writes[ref Consistency Model. Read-Your-Writes Consistency](https://en.wikipedia.org/wiki/Consistency_model#Read-your-writes_consistency)” model, i.e., guaranteeing that clients observe the changes they have just made. The consistency might be lifted to the read-your-writes level by making clients pass some token that describes the last changes known to the client.
```typescript
const order = await api
let der = await api
.createOrder();
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders({
,
// Pass the identifier of the

View File

@@ -7,10 +7,10 @@ We remember that this probability is equal to the ratio of time periods: getting
Our usage scenario looks like this:
```typescript
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders();
if (pendingOrders.length == 0) {
const order = await api
let order = await api
.createOrder();
}
```
@@ -19,10 +19,10 @@ if (pendingOrders.length == 0) {
// App restart happens here,
// and all the same requests
// are repeated
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders(); // → []
if (pendingOrders.length == 0) {
const order = await api
let order = await api
.createOrder();
}
```
@@ -36,12 +36,12 @@ However, what we could do to improve this timing remains unclear. Creating an or
What could help us here is the asynchronous operations pattern. If our goal is to reduce the collision rate, there is no need to wait until the order is *actually* created as we need to quickly propagate the knowledge that the order is *accepted for creation*. We might employ the following technique: create *a task for order creation* and return its identifier, not the order itself.
```typescript
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders();
if (pendingOrders.length == 0) {
// Instead of creating an order,
// put the task for the creation
const task = await api
let task = await api
.putOrderCreationTask();
}
```
@@ -50,7 +50,7 @@ if (pendingOrders.length == 0) {
// App restart happens here,
// and all the same requests
// are repeated
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders();
// → { tasks: [task] }
```
@@ -86,12 +86,12 @@ However, we must stress that excessive asynchronicity, though appealing to API d
Therefore, despite all the advantages of the approach, we tend to recommend applying this pattern only to those cases when they are really needed (as in the example we started with when we needed to lower the probability of collisions) and having separate queues for each case. The perfect task queue solution is the one that doesn't look like a task queue. For example, we might simply make the “order creation task is accepted and awaits execution” state a separate order status and make its identifier the future identifier of the order itself:
```typescript
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders();
if (pendingOrders.length == 0) {
// Don't call it a “task”,
// just create an order
const order = await api
let order = await api
.createOrder();
}
```
@@ -100,7 +100,7 @@ if (pendingOrders.length == 0) {
// App restart happens here,
// and all the same requests
// are repeated
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders();
/* → { orders: [{
order_id: <task identifier>,

View File

@@ -3,7 +3,7 @@
In the previous chapter, we concluded with the following interface that allows minimizing collisions while creating orders:
```typescript
const pendingOrders = await api
let pendingOrders = await api
.getOngoingOrders();
{ orders: [{

View File

@@ -44,12 +44,12 @@ Now, let's consider a scenario where the partner receives an error from the API
```typescript
// Retrieve the ongoing orders
const pendingOrders = await api
let pendingOrders = await api
.getPendingOrders();
// The partner checks the status of every
// order in its system and prepares
// the list of changes to perform
const changes =
let changes =
await prepareStatusChanges(
pendingOrders
);
@@ -80,7 +80,7 @@ Now, let's consider a scenario where the partner receives an error from the API
2. Retrying only failed sub-requests:
```typescript
const pendingOrders = await api
let pendingOrders = await api
.getPendingOrders();
let changes =
await prepareStatusChanges(
@@ -115,9 +115,9 @@ Now, let's consider a scenario where the partner receives an error from the API
```typescript
do {
const pendingOrders = await api
let pendingOrders = await api
.getPendingOrders();
const changes =
let changes =
await prepareStatusChanges(
pendingOrders
);

View File

@@ -25,17 +25,17 @@ However, there are also non-trivial problems we face while developing an SDK for
```typescript
// Request 'lungo' and 'latte' recipes
const recipes = await api
let recipes = await api
.getRecipes(['lungo', 'latte']);
// Build a map that allows to quickly
// find a recipe by its identifier
const recipeMap = new Map();
let recipeMap = new Map();
recipes.forEach((recipe) => {
recipeMap.set(recipe.id, recipe);
});
// Request offers for latte and lungo
// in the vicinity
const offers = await api.search({
let offers = await api.search({
recipes: ['lungo', 'latte'],
location
});
@@ -47,7 +47,7 @@ However, there are also non-trivial problems we face while developing an SDK for
promptUser(
'What we have found',
offers.map((offer) => {
const recipe = recipeMap
let recipe = recipeMap
.get(offer.recipe_id);
return {offer, recipe};
}));
@@ -57,11 +57,11 @@ However, there are also non-trivial problems we face while developing an SDK for
```typescript
// Request 'lungo' and 'latte' recipes
const recipes = await api
let recipes = await api
.getRecipes(['lungo', 'latte']);
// Request offers for latte and lungo
// in the vicinity
const offers = await api.search({
let offers = await api.search({
// Pass the references to the recipes,
// not their identifiers
recipes,
@@ -81,7 +81,7 @@ However, there are also non-trivial problems we face while developing an SDK for
```typescript
// Request offers for latte and lungo
// in the vicinity
const offers = await api.search({
let offers = await api.search({
recipes: ['lungo', 'latte'],
location
});
@@ -103,10 +103,10 @@ However, there are also non-trivial problems we face while developing an SDK for
```typescript
// Retrieve ongoing orders
const orders = await api
let orders = await api
.getOngoingOrders();
// Build order map
const orderMap = new Map();
let orderMap = new Map();
orders.forEach((order) => {
orderMap.set(order.id, order);
});
@@ -116,7 +116,7 @@ However, there are also non-trivial problems we face while developing an SDK for
'order_state_change',
(event) => {
// Find the corresponding order
const order = orderMap
let order = orderMap
.get(event.order_id);
// Take some actions, like
// updating the UI
@@ -135,7 +135,7 @@ However, there are also non-trivial problems we face while developing an SDK for
Once again, we face a situation where an SDK lacking important features leads to mistakes in applications that use it. It would be much more convenient for a developer if an order object allowed for subscribing to its status updates without the need to learn how it works at the transport level and how to avoid missing an event.
```typescript
const order = await api
let order = await api
.createOrder(…)
// No need to subscribe to
// the entire status change
@@ -152,9 +152,9 @@ However, there are also non-trivial problems we face while developing an SDK for
```typescript
// Request offers
const offers = await api.search(…);
let offers = await api.search(…);
// The user selects an offer
const selectedOffer =
let selectedOffer =
await promptUser(offers);
let order;

View File

@@ -58,11 +58,11 @@ It is very easy to demonstrate how coupling several subject areas in one entity
But it is not the end of the story. If the developer still wants exactly this, i.e., to show a coffee shop chain icon (if any) on the order creation button, then what should they do? Following the same logic, we should provide an even more specialized possibility to do so. For example, we can adopt the following logic: if there is a `createOrderButtonIconUrl` property in the data, the icon will be taken from this field. Developers could customize the order creation button by overwriting this `createOrderButtonIconUrl` field for every search result:
```typescript
const searchBox = new SearchBox({
let searchBox = new SearchBox({
// For simplicity, let's allow
// to override the search function
searchFunction: function (params) {
const res = await api.search(params);
let res = await api.search(params);
res.forEach(function (item) {
item.createOrderButtonIconUrl =
<the URL of the icon>;

View File

@@ -194,7 +194,7 @@ If we aren't making an SDK and have not had the task of making these components
3. To implement new buttons, we can only propose to developers to create a custom offer list component (to provide methods for selecting previous and next offers) and a custom offer panel that will call these methods. If we find a simple solution for customizing, let's say, the “Place an order” button text, this solution needs to be supported in the `OfferList` code:
```typescript
const searchBox = new SearchBox(…, {
let searchBox = new SearchBox(…, {
/* <em> */offerPanelCreateOrderButtonText:
'Drink overpriced coffee!'/* </em> */
});

View File

@@ -233,10 +233,10 @@ GET /coffee-machines/{id}/stocks
Этот совет парадоксально противоположен предыдущему. Часто при разработке API возникает ситуация, когда добавляется новое необязательное поле с непустым значением по умолчанию. Например:
```typescript
const orderParams = {
let orderParams = {
contactless_delivery: false
};
const order = api.createOrder(
let order = api.createOrder(
orderParams
);
```
@@ -244,7 +244,7 @@ const order = api.createOrder(
Новая опция `contactless_delivery` является необязательной, однако её значение по умолчанию — `true`. Возникает вопрос, каким образом разработчик должен отличить явное *нежелание* пользоваться опцией (`false`) от незнания о её существовании (поле не задано). Приходится писать что-то типа такого:
```typescript
const value = orderParams.contactless_delivery;
let value = orderParams.contactless_delivery;
if (Type(value) == 'Boolean' && value == false) {
}
@@ -257,10 +257,10 @@ if (Type(value) == 'Boolean' && value == false) {
**Хорошо**
```typescript
const orderParams = {
let orderParams = {
force_contact_delivery: true
};
const order = api.createOrder(
let order = api.createOrder(
orderParams
);
```

View File

@@ -8,7 +8,7 @@
3. Клиент запрашивает текущее состояние системы и получает пустой ответ, поскольку таймаут случился раньше, чем запрос на создание заказа дошёл до сервера:
```typescript
const pendingOrders = await
let pendingOrders = await
api.getOngoingOrders(); // → []
```
@@ -34,12 +34,12 @@ try {
lock = await api.acquireLock(ORDER_CREATION);
// Получаем текущий список
// заказов, известных системе
const pendingOrders = await
let pendingOrders = await
api.getPendingOrders();
// Если нашего заказа ещё нет,
// создаём его
if (pendingOrders.length == 0) {
const order = await api.createOrder(…)
let order = await api.createOrder(…)
}
} catch (e) {
// Обработка ошибок
@@ -62,17 +62,17 @@ try {
```typescript
// Получаем состояние
const orderState =
let orderState =
await api.getOrderState();
// Частью состояния является
// версия ресурса
const version =
let version =
orderState.latestVersion;
// Заказ можно создать,
// только если версия состояния
// не изменилась с момента чтения
try {
const task = await api
let task = await api
.createOrder(version, …);
} catch (e) {
// Если версия неверна, т.е. состояние

View File

@@ -5,16 +5,16 @@
```typescript
// Получаем состояние,
// возможно, из реплики
const orderState =
let orderState =
await api.getOrderState();
const version =
let version =
orderState.latestVersion;
try {
// Обработчик запроса на
// создание заказа прочитает
// актуальную версию
// из мастер-данных
const task = await api
let task = await api
.createOrder(version, );
} catch (e) {
@@ -29,10 +29,10 @@ try {
```typescript
// Создаёт заказ
const api = await api
let api = await api
.createOrder()
// Возвращает список заказов
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders(); // → []
// список пуст
```
@@ -42,9 +42,9 @@ const pendingOrders = await api.
Важный паттерн, который поможет в этой ситуации — это имплементация модели «read-your-writes»[ref Consistency Model. Read-Your-Writes Consistency](https://en.wikipedia.org/wiki/Consistency_model#Read-your-writes_consistency), а именно гарантии, что клиент всегда «видит» те изменения, которые сам же и внёс. Поднять уровень слабой консистентности до read-your-writes можно, если предложить клиенту самому передать токен, описывающий его последние изменения.
```typescript
const order = await api
let order = await api
.createOrder();
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders({
,
// Передаём идентификатор

View File

@@ -7,19 +7,19 @@
Наш сценарий использования, напомним, выглядит так:
```typescript
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders();
if (pendingOrder.length == 0) {
const order = await api
let order = await api
.createOrder();
}
// Здесь происходит крэш приложения,
// и те же операции выполняются
// повторно
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders(); // → []
if (pendingOrder.length == 0) {
const order = await api
let order = await api
.createOrder();
}
```
@@ -33,18 +33,18 @@ if (pendingOrder.length == 0) {
Здесь нам на помощь приходят асинхронные вызовы. Если наша цель — уменьшить число коллизий, то нам нет никакой нужды дожидаться, когда заказ будет *действительно* создан; наша цель — максимально быстро распространить по репликам знание о том, что заказ *принят к созданию*. Мы можем поступить следующим образом: создавать не заказ, а задание на создание заказа, и возвращать его идентификатор.
```typescript
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders();
if (pendingOrder.length == 0) {
// Вместо создания заказа
// размещаем задание на создание
const task = await api
let task = await api
.putOrderCreationTask();
}
// Здесь происходит крэш приложения,
// и те же операции выполняются
// повторно
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders();
// → { tasks: [task] }
```
@@ -80,18 +80,18 @@ const pendingOrders = await api.
Поэтому, при всей привлекательности идеи, мы всё же склонны рекомендовать ограничиться асинхронными интерфейсами только там, где они действительно критически важны (как в примере выше, где они снижают вероятность коллизий), и при этом иметь отдельные очереди для каждого кейса. Идеальное решение с очередями — то, которое вписано в бизнес-логику и вообще не выглядит очередью. Например, ничто не мешает нам объявить состояние «задание на создание заказа принято и ожидает исполнения» просто отдельным статусом заказа, а его идентификатор сделать идентификатором будущего заказа:
```typescript
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders();
if (pendingOrder.length == 0) {
// Не называем это «заданием» —
// просто создаём заказ
const order = await api
let order = await api
.createOrder();
}
// Здесь происходит крэш приложения,
// и те же операции выполняются
// повторно
const pendingOrders = await api.
let pendingOrders = await api.
getOngoingOrders();
/* → { orders: [{
order_id: <идентификатор задания>,

View File

@@ -3,7 +3,7 @@
В предыдущей главе мы пришли вот к такому интерфейсу, позволяющему минимизировать коллизии при создании заказов:
```typescript
const pendingOrders = await api
let pendingOrders = await api
.getOngoingOrders();
{ orders: [{

View File

@@ -42,13 +42,13 @@ POST /v1/orders/bulk-status-change
```typescript
// Получаем все текущие заказы
const pendingOrders = await api
let pendingOrders = await api
.getPendingOrders();
// Партнёр проверяет статус
// каждого из них в своей
// системе и готовит
// необходимые изменения
const changes =
let changes =
await prepareStatusChanges(
pendingOrders
);
@@ -79,7 +79,7 @@ POST /v1/orders/bulk-status-change
2. Повтор только неудавшихся подзапросов:
```typescript
const pendingOrders = await api
let pendingOrders = await api
.getPendingOrders();
let changes =
await prepareStatusChanges(
@@ -115,9 +115,9 @@ POST /v1/orders/bulk-status-change
```typescript
do {
const pendingOrders = await api
let pendingOrders = await api
.getPendingOrders();
const changes =
let changes =
await prepareStatusChanges(
pendingOrders
);

View File

@@ -24,17 +24,17 @@
```typescript
// Запрашиваем информацию о рецептах
// лунго и латте
const recipes = await api
let recipes = await api
.getRecipes(['lungo', 'latte']);
// Строим карту, позволяющую обратиться
// к данным о рецепте по его id
const recipeMap = new Map();
let recipeMap = new Map();
recipes.forEach((recipe) => {
recipeMap.set(recipe.id, recipe);
});
// Запрашиваем предложения
// лунго и латте
const offers = await api.search({
let offers = await api.search({
recipes: ['lungo', 'latte'],
location
});
@@ -45,7 +45,7 @@
promptUser(
'Найденные предложения',
offers.map((offer) => {
const recipe = recipeMap
let recipe = recipeMap
.get(offer.recipe_id);
return {offer, recipe};
})
@@ -57,11 +57,11 @@
```typescript
// Запрашиваем информацию о рецептах
// лунго и латте
const recipes = await api
let recipes = await api
.getRecipes(['lungo', 'latte']);
// Запрашиваем предложения
// лунго и латте
const offers = await api.search({
let offers = await api.search({
// Передаём не идентификаторы
// рецептов, а ссылки на объекты,
// описывающие рецепты
@@ -82,7 +82,7 @@
```typescript
// Запрашиваем предложения
// лунго и латте
const offers = await api.search({
let offers = await api.search({
recipes: ['lungo', 'latte'],
location
});
@@ -104,10 +104,10 @@
```typescript
// Получаем текущие заказы
const orders = await api
let orders = await api
.getOngoingOrders();
// Строим карту заказов
const orderMap = new Map();
let orderMap = new Map();
orders.forEach((order) => {
orderMap.set(order.id, order);
});
@@ -116,7 +116,7 @@
api.subscribe(
'order_state_change',
(event) => {
const order = orderMap
let order = orderMap
.get(event.order_id);
// Выполняем какие-то
// действия с заказом,
@@ -136,7 +136,7 @@
И вновь мы приходим к тому, что недостаточно продуманный SDK приводит к ошибкам в работе использующих его приложений. Разработчику было бы намного удобнее, если бы объект заказа позволял подписаться на свои события, не задумываясь о том, как эта подписка технически работает и как не пропустить события:
```typescript
const order = await api
let order = await api
.createOrder(…)
// Нет нужды подписываться
// на *все* события и потом
@@ -153,10 +153,10 @@
```typescript
// Получаем предложения
const offers = await api.search(…);
let offers = await api.search(…);
// Пользователь выбирает
// подходящее предложение
const selectedOffer = await promptUser(offers);
let selectedOffer = await promptUser(offers);
let order;
let offer = selectedOffer;
let numberOfTries = 0;

View File

@@ -58,11 +58,11 @@
Но на этом история не заканчивается. Если разработчик всё-таки хочет именно этого, т.е. показывать иконку сети кофеен (если она есть) на кнопке создания заказа — как ему это сделать? Из той же логики, нам необходимо предоставить ещё более частную возможность такого переопределения. Например, представим себе следующую функциональность: если в данных предложения есть поле `createOrderButtonIconUrl`, то иконка будет взята из этого поля. Тогда разработчик сможет кастомизировать кнопку заказа, подменив в данных поле `createOrderButtonIconUrl` для каждого результата поиска:
```typescript
const searchBox = new SearchBox({
let searchBox = new SearchBox({
// Предположим, что мы разрешили
// переопределять поисковую функцию
searchFunction: function (params) {
const res = await api.search(params);
let res = await api.search(params);
res.forEach(function (item) {
item.createOrderButtonIconUrl =
<URL нужной иконки>;

View File

@@ -192,7 +192,7 @@ interface IOfferPanel {
3. Для реализации новых кнопок мы можем только лишь предложить программисту реализовать свой список предложений (чтобы предоставить методы выбора предыдущего / следующего предложения) и свою панель предложений, которая эти методы будет вызывать. Даже если мы придумаем какой-нибудь простой способ кастомизировать, например, текст кнопки «Сделать заказ», его поддержка всё равно будет ответственностью компонента `OfferList`:
```typescript
const searchBox = new SearchBox(…, {
let searchBox = new SearchBox(…, {
/* <em> */offerPanelCreateOrderButtonText:
'Drink overpriced coffee!'/* </em> */
});