“This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!”

A pre-announced

Ben knows that recently wrote “JS how to function programming” series of you may not be cold, because all the theoretical stuff if it is not practical, it will be meaningless.

So Bengua went to work, trying to apply some of the ideas of functional programming.

The surprising result: this process is not difficult, but the effect is not small!

Realization idea: with the aid of the compose function for continuous asynchronous process assembly, different combinations of different business processes.

This not only improves the readability of the code, but also improves the extensibility of the code. I thought: this is probably high cohesion, low coupling

Write this note, and share with you.

The scene that

After communicating the requirements with the product for the first time, I understand the need to implement a new application process, which is as follows:

Step 1: Call the SSO interface, get the return result res_token;

Step 2: Call the create interface and get the result res_id;

Step 3: Process the string and concatenate the Url;

Step 4: Establish webSocket links;

Step 5: Get the WebSocket backend push keyword and render the page;

  • Note: The interface and parameters have been simplified

In addition to step 3 and Step 5 above, the rest are to call the interface, and there is a need to pass parameters before and after the step, can be understood as a continuous and orderly asynchronous call process.

In order to quickly respond to product requirements, Bengua quickly wrote the following code:

@param {*} appId * @param {*} tag */ export const handleGetIframeSrc = function(appId, GetsingleSignOnToken ({formSource: tag }).then(data => { return new Promise((resolve, reject) => { resolve(data.result) }) }).then(token => { const para = { appId: AppId} return new Promise((resolve, reject) => {// appId => {// appId => {// Processing strings, HandleInsIframeUrl (res, token, appId) this.setH5Id(res.result.h5Id) h5Id = res.result.h5Id resolve(h5Id) }).catch(err => { this.$message({ message: Err. Message | | 'error, type:' error '})})})}). Then (h5Id = > {/ / step 4: to establish a websocket links; Return new Promise((resolve, reject) => {webSocketInit(resolve, reject, h5Id)}). Then (doclose => {// Get the WebSocket backend push keyword and render the page; if (doclose) { this.setShowEditLink({ appId: appId, h5Id: h5Id, state: True})}}). The catch (err = > {enclosing $message ({message: err. Message | | 'errors', type: 'error' }) }) } const handleInsIframeUrl = function(res, token, AppId) {/ / url splicing const secretId = this. $store. State. The 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) }Copy the code

This code is very natural to write according to the requirements of the product and then understand them.

It’s all right, isn’t it? ðŸķ

Need to update

But you have to admit, there is an unbridgeable communication gap between the programmer and the product.

Most of it is produced by the standpoint of different, can only say: Sister Li Sister li!

So, based on the previous scenario, the requirements have been somewhat updated

In addition to the new process mentioned in the last section, also add a [edit process]

The editing process is simple: cut the second step interface of the new process, and adjust the passing parameters slightly.

So this melon directly copy and then make a simple deletion, less than 1 minute, the code of the editing process was born ~

Const handleToIframeEdit = function() {// edit iframe const {editUrl, appId, H5Id} = this.ruleform // step 1: Call sso interface, get token getsingleSignOnToken({formSource: 'ins'}).then(data => {return new Promise((resolve, reject) => {resolve(data.result)})}).then(token => {// Processing strings, 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: }) this.setNavLabel(this.headList.find(I => i.appid === appId).name) resolve(h5Id)}).then(h5Id => {// įŽŽ 3 step: Establish webSocket links; Return new Promise((resolve, reject) => {webSocketInit(resolve, reject, h5Id)}). Then (doclose => {// Get the WebSocket backend push keyword and render the page; if (doclose) { this.setShowEditLink({ appId: appId, h5Id: h5Id, state: True})}}). The catch (err = > {enclosing $message ({message: err. Message | | 'error, type:' error '})})}Copy the code

Requirement renewal

To be honest, do not blame the product, the process of doing requirements is also a process of gradually understanding requirements. It’s normal to have a change in understanding! (#^.^#) Sister Li Sister Li……

There are two processes: new process and edit process.

This time, add another recreate process

The re-creation process can be simply understood as: Before creating a new process, call a delDraft to delete the draft interface.

So far, we have generated three processes:

  1. New process;
  2. Editing process;
  3. Recreate the process;

Here’s a simple brain diagram of logic:

My intuition tells me: can’t copy a new process to modify, because this is too pull… Yes, it’s not coupled, but it’s also not cohesive, which is not what I want. So, I began to encapsulate……

Realization of the above brain map code:

/** * Is there a draft record? */ judgeIfDraftExist(item) { const para = { appId: item.appId } return appH5ifDraftExist(para).then(res => { const { editUrl, h5Id, Version} = res.result if (h5Id === -1) {// There is no draft this.handleGeTiFramesrc (item)} else {// There is a draft This.handleexitdraft (item, h5Id, version, editUrl)}}).catch(err => {console.log(err)})}, /** * Select to continue editing? */ handleExitDraft(item, h5Id, version, editUrl) {this.$confirm(' Do you want to continue editing? {confirmButtonText: 'continue editing ', cancelButtonText:' Recreate ', type: 'warning' }).then(() => { const editUrlH5Id = h5Id this.handleGetIframeSrc(item, editUrl, editUrlH5Id) }).catch(() => { this.handleGetIframeSrc(item) appH5delete({ h5Id: h5Id, version: Version})})}, /** * Create process, edit process, recreate process; */ handleGetIframeSrc(item, editUrl, editUrlH5Id) { let ws_h5Id getsingleSignOnToken({ formSource: Item.tag}).then(data => {// call the sso interface and get 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 the process and recreate the process // call the create interface and get the result res_id; AppH5create (para).then(res => {// process string, concatenate 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 | | 'errors', type: 'error'})}) else {// edit process this.handleInsiFrameURL (editUrl, token, item.appId) this.setH5Id(editUrlH5Id) ws_h5Id = editUrlH5Id this.setShowNavIframe({ appId: item.appId, state: }) this.setNavLabel(item.name) resolve(true)}})}).then(() => {// Establish webSocket link; return new Promise((resolve, reject) => { webSocketInit(resolve, reject, Ws_h5Id)})}).then(doclose => {// get websocket back end push keyword, render page; if (doclose) { this.setShowEditLink({ appId: item.appId, h5Id: ws_h5Id, state: True})}}). The catch (err = > {enclosing $message ({message: err. Message | | 'errors', type: 'error' }) }) }, handleInsIframeUrl(editUrl, token, AppId) {/ / url splicing const secretId = this. $store. State. The 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) }Copy the code

So we have the new process, the edit process, and the recreate process all integrated into the above code;

The requirements are updated

The top packaging seemed fine, but then I got scared! Think: if this time, but also add a process or change the process?? I’m going to stick with if… Else superimposed on that main function? Or do you intend to make a copy of it and then delete it?

I can see that it’s full of judgments, variable assignments, references flying around, and it ends up being ðŸ’Đ, and yes, ðŸ’Đ

I touched the left atrium of my left chest, and it told me: “Spare the man.”

As a result, this melon tried to introduce the previous blow so nb functional programming! Its power is to make the code more readable, which is what I need! Come on!!!!! Show!!!!!

Compose function

For example, for example, “compose!” Yeah, we’re gonna need this guy this time!

Remember that quote?

The combination ———— declarative data stream ———— is one of the most important tools supporting functional programming!

The basic compose function looks something like this:

function compose(... FNS){return function composed(result){var list = fns.slice(); While (list.length > 0) {result = list.pop()(result); } return result; }; }, compose, compose, compose, compose, compose, compose fns) => result => { var list = fns.slice(); While (list.length > 0) {result = list.pop()(result); } return result; };Copy the code

It can redirect the output route from one function call to another, and so on.

We don’t need to focus on what’s going on inside the black box, we just need to focus on what it is! What does it need me to type?! What is its output!

composePromise

For example, the compose function is composed synchronously, whereas in this case, we want it to be asynchronous!

So it was modified to look like this:

/** * @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))) } }Copy the code

How it works: A Promise can specify a sequence to execute a THEN, and the then function waits until the next THEN is completed. You can start a sequence using the promise.resolve () function. You can use Reduce to build a sequence.

Let’s write another little test and run it on the console!

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 // 666Copy the code

It will execute b, 3 seconds later “xhr2”, then a, 5 seconds later “xhr1”, and finally 666

You can also use debugger in the console, which is interesting:

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

Gradually become beautiful

Test passed! With the composePromise function above, we are more confident in refactoring our code with functional programming composePromise.

  • In fact, the process is effortless

The implementation is as follows:

/** * Is there a draft record? */ 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)})}, /** * Select to continue editing? */ hasDraftConfirm(item, h5Id, editUrl, version) {this.$confirm(' Do you want to continue editing? {confirmButtonText: 'continue editing ', cancelButtonText:' Recreate ', type: 'warning' }).then(() => { this.compose_editAppIframe(item, h5Id, editUrl) }).catch(() => { this.compose_reNewAppIframe(item, h5Id, version) }) },Copy the code

Knock on the blackboard! Draw the key!

/** * newappiframe (...) args) { const steps = [this.step_getDoclose, this.step_createWs, this.step_splitUrl, this.step_appH5create, this.step_getsingleSignOnToken] const handleCompose = composePromise(... steps) handleCompose(... }, /** * Edit application process * Input: item, draftH5Id, editUrl * Output: item */ compose_editAppIframe(... args) { const steps = [this.step_getDoclose, this.step_createWs, this.step_splitUrl, this.step_getsingleSignOnToken] const handleCompose = composePromise(... steps) handleCompose(... }, /** * re-create process * Input: item, draftH5Id, version * Output: item */ compose_drafappiframe (... 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) },Copy the code

We use composePromise to execute the various steps in order to execute (from right to left) the functions within; You can combine, add, delete, or modify any of the steps’ children, and you can combine any new process you want to handle the product. In addition, they are encapsulated in compose_XXX, independent of each other, and do not interfere with other external processes. At the same time, the pass parameter is also very clear, what the input is! Output is what! At a glance!

Looking at this code against the brain map, isn’t it the best interpretation of the implementation of our requirements?

To a person reading strange code, you have to tell him how the logic is, and then tell him the inner implementation of each step. That makes sense!

Function function (specific steps internal implementation) :

/** * call the sso interface, get the result 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})})}, /** * call create interface, Get the result 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])}). The catch (err = > {enclosing $message ({message: err. Message | | 'errors', type: 'error'})})}, /** * tune delDraft to remove interface; */ step_delDraftH5Id(... args) { const [item, h5Id, version] = args.flat(Infinity) return new Promise((resolve, reject) => { appH5delete({ h5Id: h5Id, version: version }).then(data => { resolve(... Args)})})}, /** * handle string, concatenate 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]}, /** * establish webSocket link; */ step_createWs(... args) { return new Promise((resolve, reject) => { webSocketInit(resolve, reject, ... Args)})}, /** * get webSocket backend push keyword, render page; */ 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) }) },Copy the code

The input and output of function functions are also clearly visible.

At this point, we can say: With the compose function and functional programming, we have encapsulated the business requirements process and clarified the input and output, making our code more readable! Scalability is better too! Isn’t that high cohesion, low coupling? !

Stage summary

You ask me what is JS functional programming combat? I can only say that this is completely from the actual combat work!!

This may result in a bit too much code for this article, but this is the actual process of changing requirements, code iteration, and transformation. (It is suggested to grasp and understand the whole article)

Of course, this is not the end of the road. The process of refactoring should be ongoing all the time.

For functional programming, simply apply the compose function, which is just a starting point!

The concepts of partial functions, function curitisation, function composition, array manipulation, time state, functional programming libraries, and so on have been covered…… We will continue to use them to sort, package, and clean up the mountain of code shit! Let it keep beautiful up! ðŸ’Đ = > ðŸ‘Đ ðŸĶ°

Above, is this share ~ see here, not as good as point a like 👍👍👍

Thanks for your support ~

I’m Anton gold, output exposed input, technology insight life! See you next time