«Волшебное использование обещаний в оптимизации производительности» Хан Цао 🌿 учит объединять запросы интерфейса.

внешний интерфейс оптимизация производительности
«Волшебное использование обещаний в оптимизации производительности» Хан Цао 🌿 учит объединять запросы интерфейса.

Всем привет, я Ханцао😈, травяная обезьяна🐒. Прерывистая кровь 🔥, сплошная скульптура из песка 🌟
Если вам нравятся мои статьи, вы можете подписаться➕Нравится и расти вместе со мной~

Предисловие ☀️

Эта статья посвящена использованию Promises для реализации Deferred, и, исходя из этого, для реализации слияния параллельных запросов интерфейса, я проведу вас через реализацию этого кода на основе объяснения идей. Конечно, прежде чем я покажу вам свой код, позвольте мне рассказать вам, почему я хочу объединить вызовы интерфейса.

这篇文章希望大家可以看看结束语~拜托啦🌿

сервисный слой 🌟

Hancao недавно сменил группу, и некоторые из используемых технологических стеков также изменились, и они также связались с grpc».此处开坑,以后会讲,但是在这个之前我会讲graphql», теперь я понимаю, что самое большое изменение заключается в том, что интерфейс, предоставляемый сервером, стал более детализированным. Поэтому мы обычно дважды инкапсулируем интерфейс на стороне сервера, чтобы обеспечить слой представления лучшим интерфейсом, вызывающим сервисы.

image.png

Инкапсуляция этого сервисного уровня заставляет клиентскую часть выполнять больше бизнес-логики и имеет следующие применения (могут быть неполными):

  • Слияние интерфейсов, инкапсуляция бизнес-логики и более простые в использовании сервисы для уровня представления
  • Защитите неразумный дизайн сервера (неразумный дизайн сервера, неразумная структура данных и неразумные имена полей блокируются на уровне службы)
  • логическое мультиплексирование

Детализация и согласованность интерфейса 🌟

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

image.png

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

// 单个
export const getMeta = async (id) => {
    // xxx
    return meta;
}
// 批量
export const getMetas = async (ids) => {
    // xxxx
    return metas;
}

И партия по-прежнему включает в себя один. Затем кто-то сказал, что мы просто открываем пакетный интерфейс для просмотра, верно?

Во-первых, получить один ресурс для getMetas([id]) , который используется для установки [], и некоторые интерфейсы предоставляют пакет, а некоторые интерфейсы не предоставляют пакет, который разделяется при использовании.

Таким образом, мы отвергаем избыточные и фрагментированные службы уровня службы и не предоставляем пакетный интерфейс для уровня представления.На уровне представления, если вы хотите вызвать одну и ту же службу, чтобы использовать разные идентификаторы для получения разных ресурсов одного типа ( например, получение списка), вам нужно написать так:

const userMetas = Promise.all(UserIds.map(userId => getUserMeta(id));

Таким образом, общее использование и реализация согласованы, то есть один идентификатор используется для получения соответствующего ресурса или работы с одной вещью. Зернистость и консистенция едины и гармоничны. Вызов интерфейса уровня представления (1 к 1) также логически разумен.

Проблемы с производительностью 🌟

Но если вы предоставляете только одиночные вызовы без какой-либо промежуточной обработки, будут большие проблемы:

  • Проблемы с производительностью (может делать много запросов к интерфейсу одновременно)
  • проблема опыта разработки

Здесь я объясняю проблему опыта разработки, потому что наш вызов интерфейса grpc не может видеть информацию об интерфейсе в сети консоли, что не способствует нашей отладке, поэтому мы будем печатать информацию об интерфейсе на консоли. Если мы вызываем десятки интерфейсов одновременно и печатаем несколько страниц напрямую, найти проблему невозможно.

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

image.png

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

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

Решение 🌟

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

image.png

  • Уровень представления по-прежнему является идентификатором для получения ресурса.
  • Уровень службы выполняет сбор и пересылку параметров и вызывает пакетный интерфейс на стороне сервера.
  • Сервер разделяет пакет для получения ресурсов

Тогда вся идея ясна, давайте начнем реализовывать ее прямо сейчас ✨~

Основная статья 📖

Реализация отложенного метода 📚

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

function Deferred() {
  if (typeof (Promise) != 'undefined' && Promise.defer) {
    return Promise.defer();
  } else if(this && this instanceof Deferred){
    this.resolve = null;
    this.reject = null;
    const _this = this;
    this.promise = new Promise((resolve, reject) => {
      _this.resolve = resolve;
      _this.reject = reject;
    });
    Object.freeze(this);
  } else {
    throw new Error();
  }
}

Как использовать отложенный:

const deferred = new Deferred();
const promise = deferred.promise;
promise.then(res => {
    // xxxx 事件A
})
async function fn() {
    const arr = await xxx;// 事件B
    promise.resolve(arr);
}

Интерпретируйте его, он эквивалентен нашему事件Aбыть в事件Bпосле этого мы можем тогда事件BПосле окончанияdeferred.promiseпровестиreslove, и вернуть результат в事件A.

отдельная реализация метода 📚

Далее давайте перейдем к теме этой статьи, как объединять запросы, здесь я привожу инструментальный метод, почему он называется отдельным, слово означает разделенный, потому что я хочу использовать его для инкапсуляции пакетного интерфейса, вызываемого сервисом, Заставьте его использовать тот же идентификатор для получения ресурса в слое представления, но он будет объединен в один при вызове службы. Так что на самом деле это означает:Разделение пакетного интерфейса

Затем я помещаю то, что хочу объяснить, в комментарии к коду, чтобы каждый мог проверить это построчно:

const separate = function (multipleApi, singleApi) {
  // 闭包,建立一个独立的作用域
  let length = 0;
  let argsList = [];
  let deferred = new Deferred();
  // 工具方法,用于在一次请求完成后对作用域变量初始化
  function init() {
    length = 0;
    argsList = [];
    deferred = new Deferred();
  }
  
  return (...args) => {
    // 收集参数
    argsList.push(args);
    return new Promise((resolve, reject) => {
      // 记录发出请求个数
      length++;
      // 记录当前请求是第几个,用作标识
      let index = length;
      // 设置定时器
      let timer = setTimeout(() => {
        // 清空定时器
        clearTimeout(timer);
        // 如果在定时器的回调中index和length相等,表示并发的请求结束了
        if (index == length) {
          // 有时候service不仅写了batchapi还写了single api,当只进行一次接口调用的时候调single api更高效
          if (length === 1 && singleApi) {
            // 兼容single api
            singleApi(...args).then(res => {
              init();
              resolve(res);
            }).catch(err => {
              reject(err);
            })
          } else {
            // 并发的多个请求结束,则调用batch接口
            multipleApi(argsList).then(resList => {
              // deferred进行resolve,通知前面调用的接口数据已经拿回来了,并把信息传递给它们
              deferred.resolve(resList);
              deferred.promise.then(resList => {
                init();
                resolve(resList[index - 1])
              });
            }).catch(err => {
              //如果batch接口报错,则reject
              deferred.reject(err);
              reject(err);
            });
          }
        } else {
          // 前面的接口调用的回调在deferred.promise的then中处理
          deferred.promise.then(resList => {
            resolve(resList[index - 1]);
          }).catch((err) => {
            // 处理错误
            reject(err);
          });
        }
      }, 0)
    });
  };
}

Общая блок-схема примерно такая:

image.png

Общий порядок выполнения сверху вниз:

  • Параллельные пять запросов и сбор информации одновременно
  • Когда запрос заканчивается, вызовите пакетный интерфейс
  • Разделите и заполните данные из пакетного интерфейса ответами на предыдущие пять запросов.

Полный код + пример 🌰

код:

function Deferred() {
  if (typeof (Promise) != 'undefined' && Promise.defer) {
    return Promise.defer();
  } else if(this && this instanceof Deferred){
    this.resolve = null;
    this.reject = null;
    const _this = this;
    this.promise = new Promise((resolve, reject) => {
      _this.resolve = resolve;
      _this.reject = reject;
    });
    Object.freeze(this);
  } else {
    throw new Error();
  }
}

const separate = function (multipleApi, singleApi) {
  let length = 0;
  let argsList = [];
  let deferred = new Deferred();

  function init() {
    length = 0;
    argsList = [];
    deferred = new Deferred();
  }

  return (...args) => {
    argsList.push(args);
    return new Promise((resolve, reject) => {
      length++;
      let index = length;
      let timer = setTimeout(() => {
        clearTimeout(timer);
        if (index == length) {
          if (length === 1 && singleApi) {
            // 兼容single api
            singleApi(...args).then(res => {
              init();
              resolve(res);
            }).catch(err => {
              reject(err);
            })
          } else {
            multipleApi(argsList).then(resList => {
              deferred.resolve(resList);
              deferred.promise.then(resList => {
                init();
                console.log(index - 1);
                resolve(resList[index - 1])
              });
            }).catch(err => {
              deferred.reject(err);
              reject(err);
            });
          }
        } else {
          deferred.promise.then(resList => {
            console.log(index - 1);
            resolve(resList[index - 1]);
          }).catch((err) => {
            reject(err);
          });
        }
      }, 0)
    });
  };
}

Пример:

async function multipleApi(arr) {
  console.log(arr)
  return arr;
}

async function singleApi(...arr) {
  console.log(arr)
  return arr;
}

const serviceApi = separate(multipleApi, singleApi);

const aa = async function() {
  const m = await Promise.all([serviceApi('m', '0', 'n'), serviceApi('m', '0', 'n'), serviceApi('m', '0', 'n')]);
  console.log('promise.all', m);

  const l = await Promise.all([serviceApi('a','b','c'), serviceApi('m', '0', 'n'), serviceApi('m', '0', 'n'), serviceApi('m', '0', 'n'), serviceApi('m', '0', 'n')]);
  console.log('promise.all', l);

  const n = await serviceApi(9, 1, 3);
  console.log('single', n);
}

aa();

результат операции:

image.png

Предстоящие:

Конечно, он еще не идеален, например:

  • Разделение обработки ошибок
  • Слияние повторяющихся запросов (запросы с повторяющимися ключами, а не запросы на один и тот же API)

Я был занят в последнее время, но я присоединюсь к дальнейшим планам.

улучшение кода

Добавлено объединение повторяющихся запросов (один и тот же ключ)

function Deferred() {
  if (typeof (Promise) != 'undefined' && Promise.defer) {
    return Promise.defer();
  } else if(this && this instanceof Deferred){
    this.resolve = null;
    this.reject = null;
    const _this = this;
    this.promise = new Promise((resolve, reject) => {
      _this.resolve = resolve;
      _this.reject = reject;
    });
    Object.freeze(this);
  } else {
    throw new Error();
  }
}

const separate = function (multipleApi, singleApi) {
  let length = 0;
  let argsList = [];
  let deferred = new Deferred();
  let paramsMap = new Map();

  function init() {
    length = 0;
    argsList = [];
    deferred = new Deferred();
    paramsMap = new Map();
  }

  return (...args) => {
    return new Promise((resolve, reject) => {
      length++;
      let requestIndex, responseIndex;
      const paramsStr = JSON.stringify(args);
      const _mapIndex = paramsMap.get(paramsStr);
      if(Number.isFinite(_mapIndex)) {
        responseIndex = _mapIndex;
      } else {
        responseIndex = length;
        argsList.push(args);
        paramsMap.set(paramsStr, responseIndex);
      }
      requestIndex = length;
      let timer = setTimeout(() => {
        clearTimeout(timer);
        if (requestIndex == length) {
          if (length === 1 && singleApi) {
            // 兼容single api
            singleApi(...args).then(res => {
              init();
              resolve(res);
            }).catch(err => {
              reject(err);
            })
          } else if(paramsMap.size === 1 && singleApi) {
            singleApi(...args).then(res => {
              deferred.resolve([res]);
              deferred.promise.then(res => {
                init();
                resolve(res)
              });
            }).catch(err => {
              deferred.reject(err);
              reject(err);
            })
          } else {
            multipleApi(argsList).then(resList => {
              deferred.resolve(resList);
              deferred.promise.then(resList => {
                init();
                resolve(resList[responseIndex - 1])
              });
            }).catch(err => {
              deferred.reject(err);
              reject(err);
            });
          }
        } else {
          deferred.promise.then(resList => {
            resolve(resList[responseIndex - 1]);
          }).catch((err) => {
            reject(err);
          });
        }
      }, 0)
    });
  };
}

async function multipleApi(arr) {
  console.log('api params', arr);
  return arr;
}

async function singleApi(...arr) {
  console.log('api params single', arr);
  return arr;
}

const serviceApi = separate(multipleApi, singleApi);

const aa = async function() {
  const m = await Promise.all([serviceApi('m', '0', 'n'), serviceApi('1'), serviceApi('m', '0', 'n'), serviceApi('m', '0', 'n')]);
  console.log('M promise.all', m);

  // const l = await Promise.all([serviceApi('a','b','c'), serviceApi('m', '0', 'n'), serviceApi('m', '0', 'n'), serviceApi('m', '0', 'n'), serviceApi('m', '0', 'n')]);

  // const n = await serviceApi(9, 1, 3);

  const n = await Promise.all([serviceApi('1'), serviceApi('1'), serviceApi('1')]);
  console.log('N promise.all', n);
}

aa();

// export default batchApi;


Вывод ☀️

image.png

Тогда эта статья закончилась, у меня плохое настроение в последнее время💢, хочу сказать в заключительном слове"但是考虑到我的文章还是会有一些人看到,不能传递负能量,所以我在发之前删掉了一大串文字", тогда позвольте мне поговорить о чем-то, связанном с жизнью. Я чувствую, что писательство, кодирование и работа заняли мою жизнь в последнее время, но я не стальной человек. Людям нужно отдохнуть и привести свое состояние в порядок, поэтому я могу дать Возьмите отпуск в одиночестве, расслабьтесь и расслабьтесь, как умственно, так и физически~

Я написал эти два плана ранним утром 8.21.Вы видите, что я человек, который любит экспериментировать.Я хочу сделать свою игру и платформу для визуальной генерации html.Вы можете ожидать его,может быть один день, когда вы можете сыграть со мной в игру, использовать мою платформу, чтобы сделать html-поздравительную открытку в своем собственном стиле, может быть, когда-нибудь больше людей смогут узнать Ханьцао через интересные вещи🌿.

План холодной травы:

  1. Перевод Vscode API (рабочая область)
  2. Визуальное перетаскивание для создания HTML-платформы для поздравительных открыток (поддержка творческой мастерской)
  3. hancao.Game (возможно, кокосы)
  4. commiui (как действовать)

寒草,不只是个前端,请伙伴们期待

Статьи о холодной траве планируют опубликовать:

  1. Алгоритмы/Основы работы с компьютером (группы подсчета, компьютерные сети, принципы компиляции, операционные системы)
  2. Попытки в большем количестве областей, более глубокое исследование одной проблемы
  3. Демо/дизайн в моем собственном стиле
  4. Основы внешнего интерфейса (но я мало знаю о своем обычно используемом стеке технологий)

寒草,不只是个前端,请伙伴们期待

И, благодаря Наггетс за то, что дали мне познакомиться со многими партнерами и позволили мне делать больше забавных вещей, в этом месяце я встретил Луо Чжу, Фей Гэ, Саньсинь, Дашуай, Дашэн (я желаю учителю Дашэн все больше и больше ослеплять в будущее, банкноты продолжают считать), Yu Fei, Ziyi, DevUI Karge, Zero One, Wind Wheel и др., а также различные летающие травой банкнотные инженеры огонь-земля-дракон, а также читатели и друзья, желающие усердно работать со мной, чтобы расти и общаться, спасибо всем, было приятно познакомиться 🌟

Спасибо также за наставление темы, Mr.

image.png

Холодная трава 🌿 всегда здесь~
Пожалуйста, продолжайте ждать меня, у меня еще есть ряд больших работ, но позвольте мне держать это в секрете☀️, дайте мне месяц на подготовку

写在最后
Какая мне польза от Бога и Будды
Кто может восхвалять реализацию Дао
Пламя превращается в скорпиона🔥
Золотая палка Сюаньцзя, сотрясающая Небесный дворец

где дорога
Дорога у ваших ног 🌟

Друзья, если вам понравилась моя статья, вы можете поставить лайк 👍 подписаться ➕ , это самая большая поддержка для меня.

добавь меня в WeChat:hancao97, приглашаем вас присоединиться к группе, узнать о текущей ситуации в группе Hancao 🌿 на github, вместе изучить интерфейс и стать лучшим инженером~ (групповой QR-код находится здесь ->Передняя часть спит поздно,Если срок действия QR-кода истек, посмотрите комментарии в точке кипения ссылки.Я размещу последний QR-код в области комментариев.Конечно, вы также можете добавить меня в WeChat и я притяну вас в группу.После все, я тоже интересный фронтенд, и меня не плохо знать 🌟 ~)