Как стать автором
Обновить

Установка токена в запросы Axios асинхронно

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

Здесь используются: Express.js, TypeScript, Axios, Mongoose.

В рамках автоматизации некоторых процессов работы с сервисом Мой Склад (moyskald.ru) потребовалось написать блок для работы с их API. Сервис позволяет получать и обновлять токен, который потом нужно добавлять в заголовки всех запросов.

Первоначальный вариант

Первоначально полученный token хранился в файле конфета и код создания инстанса выглядел так:

// moyskladHttp.ts

import axios from 'axios'
import * as moySkaldConfig from '../../../config/moySklad'

const moyskladHttp = axios.create({
    baseURL: moySkaldConfig.baseUrl,
    headers: {
        'Authorization': `Bearer ${moySkaldConfig.token}`
    },
    validateStatus: () => true
})

export default moyskladHttp

В этом варианте не устраивала потенциальная возможность смены токена. Пришлось бы править код и заново разворачивать все на хостинге. Поэтому было решено хранить токен в базе данных (используется  Mongo). Здесь его можно оперативно изменить напрямую или через через веб-интерфейс.

Исправленный вариант

Простой вариант - создать инстанс без заголовка авторизации и добавлять его уже при каждом запросе. Выглядит это так:

const moyskladHttp = axios.create({
    baseURL: moySkaldConfig.baseUrl,
    validateStatus: () => true
})

moyskladHttp.interceptors.request.use(async (config) => {
    const token =  await getToken()
    return {
        ...config,
        headers: { ...config.headers, Authorization: `Bearer ${token}` }
    }
})

функция getToken() делает асинхронный запрос к нашей базе и возвращает токен. Она выглядит так:

const getToken = async () => {
    const doc = await Credential.findOne({ key: 'moyskladToken' })
    return doc?.value || ''
}

Credential - это модель mongoose для нашей базы. Токены хранятся в коллекции ‘credentials’ и документы имеют вид: {key: string, value: string}

Имеющееся решение плохо тем, что возникает дополнительная задержка при каждом запросе к API сервиса Мой Склад. Так как нужно получить сначала токен из базы. С одной стороны, специфика наших задач не требует максимальной скорости работы сервиса. То есть проблема совсем не критичная. С другой, решение вопроса довольно простое.

Для этого обернем функцию getToken() вспомогательной функцией _getToken(), которая будет хранить единожды полученный токен. Выглядит она так:

const _getToken = () => {
    let token = ''

    return async () => {
        if (token !== '') return token
        const doc = await Credential.findOne({ key: 'moyskladToken' })
        token = doc?.value || ''
        return token
    }
}

const getToken = _getToken()

Переменная token доступна хранится в функции _getToken() и доступна при этом внутри нашей getToken(). Здесь дополнительно производится проверка: если токен еще не задан, то вытягиваем его из базы. Если получилось, то записываем его в переменную token (сам токен или пустую строку, в случае какой-либо ошибки). Таким образом, единожды полученный токен будет выдаваться сразу, минуя обращение к базе.

Целиком код выглядит так:

import axios from 'axios'
import * as moySkaldConfig from '../../../config/moySklad'
import Credential from '../../../models/credential.models'

const _getToken = () => {

    let token = ''

    return async () => {
        if (token !== '') return token
        const doc = await Credential.findOne({ key: 'moyskladToken' })
        token = doc?.value || ''
        return token
    }
}
const getToken = _getToken()

const moyskladHttp = axios.create({
    baseURL: moySkaldConfig.baseUrl,
    validateStatus: () => true
})


moyskladHttp.interceptors.request.use(async (config) => {
    const token =  await getToken()
    return {
        ...config,
        headers: { ...config.headers, Authorization: `Bearer ${token}`}
    }
})

export default moyskladHttp

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.