Благодаря функции компоновки моя дерьмовая гора кода 💩 постепенно становилась красивее~

интервью внешний интерфейс JavaScript
Благодаря функции компоновки моя дерьмовая гора кода 💩 постепенно становилась красивее~

«Эта статья участвовала в мероприятии Haowen Convocation Order, щелкните, чтобы просмотреть:Двойные заявки на внутреннюю и внешнюю стороны, призовой фонд в 20 000 юаней ждет вас, чтобы бросить вызов!"

говори первым

Бенгуа знает, что недавно написанная серия «Как использовать JS и функциональное программирование» может быть не очень популярной, потому что все теоретические вещи будут бессмысленны, если они будут оторваны от реальных боевых действий.

I6cDpC.th.png

Поэтому Бенгуа начал развивать практическую работу и пытался применить некоторые идеи функционального программирования.

Последнее неожиданное открытие:Процесс выполнения не сложный, а эффект не маленький!

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

Это не только улучшает кодудобочитаемость, что также улучшает кодРасширяемость. Я подумал: может быть, этоВысокая сплоченность, низкая связанностьбар~

Напишите эту статью и поделитесь ею с вами.

описание сцены

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

Шаг 1: Вызовите интерфейс sso и получите возвращаемый результат res_token;

Шаг 2: Вызовите интерфейс создания и получите возвращаемый результат res_id;

Шаг 3: Обработайте строки, объедините URL-адреса;

Шаг 4: Установите соединение через веб-сокет;

Шаг 5: Получите ключевое слово websocket backend push и отобразите страницу;

  • Примечание: интерфейс и параметры были несколько упрощены.

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

Чтобы быстро реагировать на спрос на продукцию, Bengua быстро написала следующий код:

/**
 * 新建流程
 * @param {*} appId
 * @param {*} tag
 */

export const handleGetIframeSrc = function(appId, tag) {
  let h5Id
// 第 1 步: 调用 sso 接口,获取token
  getsingleSignOnToken({ formSource: tag }).then(data => { 
    return new Promise((resolve, reject) => {
      resolve(data.result)
    })
  }).then(token => { 
    const para = { appId: appId }
    return new Promise((resolve, reject) => {
// 第 2 步: 调用 create 接口,新建应用
      appH5create(para).then(res => {
// 第 3 步: 处理字符串,拼接 Url
        this.handleInsIframeUrl(res, token, appId)
        this.setH5Id(res.result.h5Id)
        h5Id = res.result.h5Id
        resolve(h5Id)
      }).catch(err => {
        this.$message({
          message: err.message || '出现错误',
          type: 'error'
        })
      })
    })
  }).then(h5Id => { 
// 第 4 步:建立 websocket 链接;
    return new Promise((resolve, reject) => {
      webSocketInit(resolve, reject, h5Id)
    })
  }).then(doclose => {
// 第 5 步:拿到 websocket 后端推送关键字,渲染页面;
    if (doclose) { this.setShowEditLink({ appId: appId, h5Id: h5Id, state: true }) }
  }).catch(err => {
    this.$message({
      message: err.message || '出现错误',
      type: 'error'
    })
  })
}

const handleInsIframeUrl = function(res, token, appId) { 
// url 拼接
  const secretId = this.$store.state.userinfo.enterpriseList[0].secretId
  let editUrl = res.result.editUrl
  const infoId = editUrl.substr(editUrl.indexOf('?') + 1, editUrl.length - editUrl.indexOf('?'))
  editUrl = res.result.editUrl.replace(infoId, `from=a2p&${infoId}`)
  const headList = JSON.parse(JSON.stringify(this.headList))
  headList.forEach(i => {
    if (i.appId === appId) { i.srcUrl = `${editUrl}&token=${token}&secretId=${secretId}` }
  })
  this.setHeadList(headList)
}

Этот код очень естественно пишется под требования продукта, а потом сам его понимает.

На самом деле нормально, да? 🐶

Обновление требований

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

Большая часть этого создается тем, что вы стоите под разными углами, вы можете только сказать: сестра Ли, сестра Ли!

Итак, исходя из предыдущего сценария, спрос происходит немноговозобновить ~

I6UGrz.th.png

В дополнение к вышеупомянутому【Новый процесс】, плюс еще один【Процесс редактирования】╮(╯▽╰)╭

Процесс редактирования прост: отрежьте второй шаг нового интерфейса процесса, а затем немного подкорректируйте параметры.

Поэтому Бен Гуа напрямую скопировал его и сделал простые удаления.Менее чем за 1 минуту родился код для процесса редактирования~

/**
 * 编辑流程
 */
 
const handleToIframeEdit = function() { // 编辑 iframe
  const { editUrl, appId, h5Id } = this.ruleForm
// 第 1 步: 调用 sso 接口,获取token
  getsingleSignOnToken({ formSource: 'ins' }).then(data => {
    return new Promise((resolve, reject) => {
      resolve(data.result)
    })
  }).then(token => { 
// 第 2 步:处理字符串,拼接 Url
    return new Promise((resolve, reject) => {
      const secretId = this.$store.state.userinfo.enterpriseList[0].secretId
      const infoId = editUrl.substr(editUrl.indexOf('?') + 1, editUrl.length - editUrl.indexOf('?'))
      const URL = editUrl.replace(infoId, `from=a2p&${infoId}`)
      const headList = JSON.parse(JSON.stringify(this.headList))
      headList.forEach(i => {
        if (i.appId === appId) { i.srcUrl = `${URL}&token=${token}&secretId=${secretId}` }
      })
      this.setHeadList(headList)
      this.setShowEditLink({ appId: appId, h5Id: h5Id, state: false })
      this.setShowNavIframe({ appId: appId, state: true })
      this.setNavLabel(this.headList.find(i => i.appId === appId).name)
      resolve(h5Id)
    })
  }).then(h5Id => {
// 第 3 步:建立 websocket 链接;
    return new Promise((resolve, reject) => {
      webSocketInit(resolve, reject, h5Id)
    })
  }).then(doclose => {
// 第 4 步:拿到 websocket 后端推送关键字,渲染页面;
    if (doclose) { this.setShowEditLink({ appId: appId, h5Id: h5Id, state: true }) }
  }).catch(err => {
    this.$message({
      message: err.message || '出现错误',
      type: 'error'
    })
  })
}

нужно обновить

Честно говоря, я не виню продукт, процесс выдвижения требований — это еще и процесс их постепенного понимания. Изменения в понимании - это нормально! (#^.^#) Ли Цзе Ли Цзе......

I6UIKu.th.png

Выше есть два процесса:Создать новый процесс, изменить процесс.

На этот раз еще одинВоссоздайте процесс ~

Процесс воссоздания можно просто понять как: вызов delDraft для удаления чернового интерфейса перед созданием нового процесса;

На данный момент мы сгенерировали три процесса:

  1. новый процесс;
  2. процесс редактирования;
  3. воссоздать процесс;

Вот простая карта мозга, иллюстрирующая логику:

I6Xi9Q.png

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

Код для реализации приведенной выше карты мозга:

/**
 * 判断是否存在草稿记录?
 */
judgeIfDraftExist(item) {
  const para = { appId: item.appId }
  return appH5ifDraftExist(para).then(res => {
    const { editUrl, h5Id, version } = res.result
    if (h5Id === -1) { // 不存在草稿
      this.handleGetIframeSrc(item)
    } else { // 存在草稿
      this.handleExitDraft(item, h5Id, version, editUrl)
    }
  }).catch(err => {
    console.log(err)
  })
},
/**
 * 选择继续编辑?
 */
handleExitDraft(item, h5Id, version, editUrl) {
  this.$confirm('有未完成的信息收集链接,是否继续编辑?', '提示', {
    confirmButtonText: '继续编辑',
    cancelButtonText: '重新创建',
    type: 'warning'
  }).then(() => {
    const editUrlH5Id = h5Id
    this.handleGetIframeSrc(item, editUrl, editUrlH5Id)
  }).catch(() => {
    this.handleGetIframeSrc(item)
    appH5delete({ h5Id: h5Id, version: version })
  })
},
/**
 * 新建流程、编辑流程、重新创建流程;
 */
handleGetIframeSrc(item, editUrl, editUrlH5Id) {
  let ws_h5Id
  getsingleSignOnToken({ formSource: item.tag }).then(data => { 
// 调用 sso 接口,拿到返回结果 res_token;
    return new Promise((resolve, reject) => {
      resolve(data.result)
    })
  }).then(token => {
    const para = { appId: item.appId }
    return new Promise((resolve, reject) => {
      if (!editUrl) { // 新建流程、重新创建流程
// 调用 create 接口,拿到返回结果 res_id;
        appH5create(para).then(res => {
// 处理字符串,拼接 Url;
          this.handleInsIframeUrl(res.result.editUrl, token, item.appId)
          this.setH5Id(res.result.h5Id)
          ws_h5Id = res.result.h5Id
          this.setShowNavIframe({ appId: item.appId, state: true })
          this.setNavLabel(item.name)
          resolve(true)
        }).catch(err => {
          this.$message({
            message: err.message || '出现错误',
            type: 'error'
          })
        })
      } else { // 编辑流程
        this.handleInsIframeUrl(editUrl, token, item.appId)
        this.setH5Id(editUrlH5Id)
        ws_h5Id = editUrlH5Id
        this.setShowNavIframe({ appId: item.appId, state: true })
        this.setNavLabel(item.name)
        resolve(true)
      }
    })
  }).then(() => { 
// 建立 websocket 链接;
    return new Promise((resolve, reject) => {
      webSocketInit(resolve, reject, ws_h5Id)
    })
  }).then(doclose => {
// 拿到 websocket 后端推送关键字,渲染页面;
    if (doclose) { this.setShowEditLink({ appId: item.appId, h5Id: ws_h5Id, state: true }) }
  }).catch(err => {
    this.$message({
      message: err.message || '出现错误',
      type: 'error'
    })
  })
},

handleInsIframeUrl(editUrl, token, appId) {
// url 拼接
  const secretId = this.$store.state.userinfo.enterpriseList[0].secretId
  const infoId = editUrl.substr(editUrl.indexOf('?') + 1, editUrl.length - editUrl.indexOf('?'))
  const url = editUrl.replace(infoId, `from=a2p&${infoId}`)
  const headList = JSON.parse(JSON.stringify(this.headList))
  headList.forEach(i => {
    if (i.appId === appId) { i.srcUrl = `${url}&token=${token}&secretId=${secretId}` }
  })
  this.setHeadList(headList)
}

Таким образом, мы будемНовый процесс, Редактировать процесс, Воссоздать процессВсе интегрировано в приведенный выше код;

Нужно обновить снова

Пакет выше, кажется, в порядке, но на данный момент я напуган! Подумай: если на этот разНужно добавить или изменить процесс? ? ?Буду ли я продолжать складывать if...else внутри этой основной функции? Или вы планируете сделать прямую копию, а затем удалить ее?

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

Я коснулся левого предсердия левой груди, и оно сказало мне:"Прости пикапера~"

Итак, Bengua попыталась внедрить столь популярное прежде функциональное программирование! его способностьсделать код более читаемым, это то, что мне нужно! давай! ! экспонат! !

I6cPMf.png

составить функцию

мы в«XDM, как функциональное программирование на JS? Смотрите, хватит! (три)"В этой статье рассказывается о композиции функций compose! Верно, на этот раз мы собираемся использовать этого парня!

Помните эту фразу?

Композиция — Декларативный поток данных — один из самых важных инструментов, лежащих в основе функционального программирования!

Самая простая функция компоновки выглядит так:

function compose(...fns) {
    return function composed(result){
        // 拷贝一份保存函数的数组
        var list = fns.slice();
        while (list.length > 0) {
            // 将最后一个函数从列表尾部拿出
            // 并执行它
            result = list.pop()( result );
        }
        return result;
    };
}

// ES6 箭头函数形式写法
var compose =
    (...fns) =>
        result => {
            var list = fns.slice();
            while (list.length > 0) {
                // 将最后一个函数从列表尾部拿出
                // 并执行它
                result = list.pop()( result );
            }
            return result;
        };

Он может перенаправить маршрут вывода одного вызова функции на вызов другой функции и т. д.

I6c6uy.png

Нам не нужно сосредотачиваться на том, что происходит в черном ящике, просто сосредоточьтесь на том, что это за штука (функция)! Мне нужно что-то ввести! Каков его выход!

composePromise

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

Поэтому он был преобразован в это:

/**
 * @param  {...any} args
 * @returns
 */

export const composePromise = function(...args) {
  const init = args.pop()
  return function(...arg) {
    return args.reverse().reduce(function(sequence, func) {
      return sequence.then(function(result) {
        // eslint-disable-next-line no-useless-call
        return func.call(null, result)
      })
    }, Promise.resolve(init.apply(null, arg)))
  }
}

принцип:Обещание может указать последовательность, чтобы указать процесс выполнения, затем функция then будет ждать, пока выполнение не будет завершено, прежде чем выполнять следующую обработку then. Чтобы запустить последовательность, используйте функцию Promise.resolve(). Чтобы построить последовательность, вы можете использовать reduce .

Давайте напишем небольшой тест и запустим его на консоли!

let compose = function(...args) {
  const init = args.pop()
  return function(...arg) {
    return args.reverse().reduce(function(sequence, func) {
      return sequence.then(function(result) {
        return func.call(null, result)
      })
    }, Promise.resolve(init.apply(null, arg)))
  }
}

let a = async() => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('xhr1')
      resolve('xhr1')
    }, 5000)
  })
}

let b = async() => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('xhr2')
      resolve('xhr2')
    }, 3000)
  })
}
let steps = [a, b] // 从右向左执行
let composeFn = compose(...steps)

composeFn().then(res => { console.log(666) })

// xhr2
// xhr1
// 666

Сначала он выполнит b, выведет «xhr2» через 3 секунды, затем a, выведет «xhr1» через 5 секунд и, наконец, выведет 666.

Так же можно попробовать в консоли с отладчиком параметров, очень интересно:

composeFn(1, 2).then(res => { console.log(66) })

постепенно становиться красивым

Тест пройден! С помощью приведенной выше функции composePromise мы более уверенно используем функциональное программирование composePromise.рефакторингнаш код.

  • На самом деле, этот процесс совсем не трудоемкий~

Реализация выглядит следующим образом:

/**
 * 判断是否存在草稿记录?
 */
handleJudgeIfDraftExist(item) {
    return appH5ifDraftExist({ appId: item.appId }).then(res => {
      const { editUrl, h5Id, version } = res.result
      h5Id === -1 ? this.compose_newAppIframe(item) : this.hasDraftConfirm(item, h5Id, editUrl, version)
    }).catch(err => {
      console.log(err)
    })
},
/**
 * 选择继续编辑?
 */
hasDraftConfirm(item, h5Id, editUrl, version) {
    this.$confirm('有未完成的信息收集链接,是否继续编辑?', '提示', {
      confirmButtonText: '继续编辑',
      cancelButtonText: '重新创建',
      type: 'warning'
    }).then(() => {
      this.compose_editAppIframe(item, h5Id, editUrl)
    }).catch(() => {
      this.compose_reNewAppIframe(item, h5Id, version)
    })
},

Стучите по доске! Ставь точку!

/**
* 新建应用流程
* 入参: item
* 输出:item
*/
compose_newAppIframe(...args) {
    const steps = [this.step_getDoclose, this.step_createWs, this.step_splitUrl, this.step_appH5create, this.step_getsingleSignOnToken]
    const handleCompose = composePromise(...steps)
    handleCompose(...args)
},
/**
* 编辑应用流程
* 入参: item, draftH5Id, editUrl
* 输出:item
*/
compose_editAppIframe(...args) {
    const steps = [this.step_getDoclose, this.step_createWs, this.step_splitUrl, this.step_getsingleSignOnToken]
    const handleCompose = composePromise(...steps)
    handleCompose(...args)
},
/**
* 重新创建流程
* 入参: item,draftH5Id,version
* 输出:item
*/
compose_reNewAppIframe(...args) {
    const steps = [this.step_getDoclose, this.step_createWs, this.step_splitUrl, this.step_appH5create, this.step_getsingleSignOnToken, this.step_delDraftH5Id]
    const handleCompose = composePromise(...steps)
    handleCompose(...args)
},

Мы выполняем различные шаги через composePromise для последовательного выполнения функций (справа налево); вы можете произвольно комбинировать, добавлять, удалять или изменять подэлементы шагов, и вы можете произвольно комбинировать новые процессы для работы с продуктами. При этом все они инкапсулированы в compose_xxx, которые не зависят друг от друга и не будут мешать другим внешним процессам. При этом по параметрам тоже очень понятно, какой вход! Какой выход! С одного взгляда!

Глядя на этот код на карте мозга, разве это не лучшая интерпретация наших потребностей?

Для человека, читающего незнакомый код, вы должны рассказать ему, в чем логика, а затем рассказать ему внутреннюю реализацию каждого шага. Вот что имеет смысл!

I6Xi9Q.png

Функциональная функция (внутренняя реализация конкретных шагов):

/**
* 调用 sso 接口,拿到返回结果 res_token;
*/
step_getsingleSignOnToken(...args) {
    const [item] = args.flat(Infinity)
    return new Promise((resolve, reject) => {
      getsingleSignOnToken({ formSource: item.tag }).then(data => {
        resolve([...args, data.result]) // data.result 即 token
      })
    })
},
/**
*  调用 create 接口,拿到返回结果 res_id;
*/
step_appH5create(...args) {
    const [item, token] = args.flat(Infinity)
    return new Promise((resolve, reject) => {
      appH5create({ appId: item.appId }).then(data => {
        resolve([item, data.result.h5Id, data.result.editUrl, token])
      }).catch(err => {
        this.$message({
          message: err.message || '出现错误',
          type: 'error'
        })
      })
    })
},
/**
* 调 delDraft 删除接口;
*/
step_delDraftH5Id(...args) {
    const [item, h5Id, version] = args.flat(Infinity)
    return new Promise((resolve, reject) => {
      appH5delete({ h5Id: h5Id, version: version }).then(data => {
        resolve(...args)
      })
    })
},
/**
*  处理字符串,拼接 Url;
*/
step_splitUrl(...args) {
    const [item, h5Id, editUrl, token] = args.flat(Infinity)
    const infoId = editUrl.substr(editUrl.indexOf('?') + 1, editUrl.length - editUrl.indexOf('?'))
    const url = editUrl.replace(infoId, `from=a2p&${infoId}`)
    const headList = JSON.parse(JSON.stringify(this.headList))
    headList.forEach(i => {
      if (i.appId === item.appId) { i.srcUrl = `${url}&token=${token}` }
    })
    this.setHeadList(headList)
    this.setH5Id(h5Id)
    this.setShowNavIframe({ appId: item.appId, state: true })
    this.setNavLabel(item.name)
    return [...args]
},
/**
*  建立 websocket 链接;
*/
step_createWs(...args) {
    return new Promise((resolve, reject) => {
      webSocketInit(resolve, reject, ...args) 
})
  },
/**
*  拿到 websocket 后端推送关键字,渲染页面;
*/
step_getDoclose(...args) {
    const [item, h5Id, editUrl, token, doclose] = args.flat(Infinity)
    if (doclose) { this.setShowEditLink({ appId: item.appId, h5Id: h5Id, state: true }) }
    return new Promise((resolve, reject) => {
      resolve(true)
    })
},

Вход и выход функции function также хорошо видны.

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

I6UWZD.th.png

резюме этапа

Вы спрашиваете меня, что такое функциональное программирование JS в действии? Могу только сказать, что эта статья полностью из реального боя на работе! ! !

В результате объем кода в этой статье может быть немного большим, но это реальное изменение спроса, процесс итерации и преобразования кода. (рекомендуется усвоить и понять во всем)

Конечно, это не конец, процесс рефакторинга кода должен идти постоянно.

Для функционального программирования простое применение функции compose — это только отправная точка!

Мы уже говорили о концепциях частичных функций, каррировании функций, композиции функций, операциях с массивами, временных состояниях, библиотеках функционального программирования и т. д. Мы продолжим использовать их для классификации, упаковки, очистки! Держите это красиво! 💩 => 👩‍🦰

Выше это этот обмен~ Если вы видите все это здесь, почему вам это не нравится👍👍👍

Спасибо за вашу поддержку~

Я Энтони Наггетс, вывод раскрывает ввод, а техническое понимание — это жизнь! Увидимся в следующий раз~