8 Tips for Advanced Uses of Promises

I find that many people only know how to use promises regularly.

In js projects, the use of promises should be essential, but I found that among colleagues and interviewers, many intermediate and above front-ends still insist on conventional usages such as promiseInst.then(), promiseInst.catch(), Promise, etc. . Even async/await they just know about it but don't know why they use it.

But in fact, Promise has many clever advanced uses, and some advanced uses are also widely used within the alova request strategy library.

Now, I will share with you 8 advanced usage tips. Hope these tips are helpful to you, now, let’s get started.

1. Serial execution of Promise array

For example, if you have a set of interfaces that need to be executed serially, you may first think of using await.

const requestAry = [() => api.request1(), () => api.request2(), () => api.request3()];for (const requestItem of requestAry) {
   
     await requestItem();}

If you use promises, you can use the then function to concatenate multiple promises to achieve serial execution.

​​​​​​​

const requestAry = [() => api.request1(), () => api.request2(), () => api.request3()];const finallyPromise = requestAry.reduce(     (currentPromise, nextRequest) => currentPromise.then(() => nextRequest()),     Promise.resolve() // Create an initial promise for linking promises in the array);

2. Change state outside the scope of the new Promise

Suppose you have multiple pages whose functionality requires collecting user information before allowing use. Before clicking to use a function, a pop-up box will pop up to collect information. How would you implement this?

The following are the implementation ideas of front-end students at different levels:

Elementary front-end: I write a modal box and then copy and paste it to other pages. Very efficient!

Intermediate front-end: This is difficult to maintain. We need to encapsulate this component separately and introduce it on the required page!

Advanced Front End: Install anything sealed! ! ! Wouldn't it be better to write the method call in a place where it can be called on all pages?

To understand how the advanced front-end is implemented, take vue3 as an example and take a look at the following example.

​​​​​​​

<!-- App.vue --><template><!-- The following is the modal box component -->   <div class="modal" v-show="visible">     <div>       User name: <input v-model="info.name" />     </div>     <!-- Other information -->     <button @click="handleCancel">Cancel</button>     <button @click="handleConfirm">Submit</button>   </div>   <!-- Page components --></template><script setup>import { provide } from 'vue';const visible = ref(false);const info = reactive({
   
      name: ''});let resolveFn, rejectFn;// Pass the information collection function to the followingprovide('getInfoByModal', () => {
   
      visible.value = true;   return new Promise((resolve, reject) => {
   
        // Assign the two functions to the outside and break through the promise scope     resolveFn = resolve;     rejectFn = reject;   });})const handleConfirm = () => {
   
      resolveFn && resolveFn(info);};const handleCancel = () => {
   
      rejectFn && rejectFn(new Error('User has canceled'));};</script>

Next, getInfoByModal can easily obtain the data filled in by the user by directly calling the modal box.

​​​​​​​

<template>   <button @click="handleClick">Fill in the information</button></template>
<script setup>import { inject } from 'vue';const getInfoByModal = inject('getInfoByModal');const handleClick = async () => {
   
      // After the call, the modal box will be displayed. After the user clicks to confirm, the promise will be changed to the fullfilled state to obtain the user information.   const info = await getInfoByModal();   await api.submitInfo(info);}</script>

This is also a way to encapsulate common components in many UI component libraries.

3. Alternative usage of async/await

Many people only know that it is used to receive the return value of the async function when calling await, but they do not know that the async function is actually a function that returns a promise. For example, the following two functions are equivalent:

​​​​​​​

const fn1 = async () => 1;const fn2 = () => Promise.resolve(1);
fn1(); // Also returns a promise object with a value of 1

In most cases, await will follow the Promise object and wait for it to be completely filled. Therefore, the following fn1 function wait is also equivalent:

​​​​​​​

await fn1();
const promiseInst = fn1();await promiseInst;

However, await also has a little-known secret. When the value followed by it is not a promise object, it will wrap the value with a promise object, so the code after await must be executed asynchronously. example:

​​​​​​​

Promise.resolve().then(() => {
   
     console.log(1);});await 2;console.log(2);//Print order bits: 1 2

Equivalent to

​​​​​​​

Promise.resolve().then(() => {
   
     console.log(1);});Promise.resolve().then(() => {
   
     console.log(2);});

4. Commitment to implement requested sharing

When a request has been sent but has not received a response, sending the same request again will cause a waste of requests. At this point, we can share the response from the first request with the second request.

​​​​​​​

request('GET', '/test-api').then(response1 => {
   
     // ...});request('GET', '/test-api').then(response2 => {
   
     // ...});

The above two requests are actually sent only once and receive the same response value at the same time.

So, what are the usage scenarios for request sharing? I think there are three:

  • When the page renders multiple internal components to obtain data at the same time;

  • The submit button is not disabled and the user clicks the submit button multiple times in succession;

  • In the case of preloading data, enter the preloading page before the preloading is completed;

This is also one of alova's advanced features. To implement request sharing, you need to use the cache function of promise, that is, a promise object can obtain data through multiple awaits. The simple implementation idea is as follows:

​​​​​​​

const pendingPromises = {};function request(type, url, data) {
   
      // Use the request information as the only request key to cache the promise object being requested   //Requests with the same key will reuse promise   const requestKey = JSON.stringify([type, url, data]);   if (pendingPromises[requestKey]) {
   
        return pendingPromises[requestKey];   }   const fetchPromise = fetch(url, {
   
        method: type,     data: JSON.stringify(data)   })   .then(response => response.json())   .finally(() => {
   
        delete pendingPromises[requestKey];   });   return pendingPromises[requestKey] = fetchPromise;}

The above two requests are actually sent only once and receive the same response value at the same time.

So, what are the usage scenarios for request sharing? I think there are three:

  • When the page renders multiple internal components to obtain data at the same time;

  • The submit button is not disabled and the user clicks the submit button multiple times in succession;

  • In the case of preloading data, enter the preloading page before the preloading is completed;

This is also one of alova's advanced features. To implement request sharing, you need to use the cache function of promise, that is, a promise object can obtain data through multiple awaits. The simple implementation idea is as follows:

​​​​​​​

const promise = new Promise((resolve, reject) => {
   
     resolve();  reject();});

The correct answer is full status. We just need to remember that once a pending promise moves from one state to another, it cannot be changed. Therefore, in the example, it changes to the fulfilled state first, and then reject() will not change to the rejected state again.

5. Completely clarify the then/catch/finally return value

In a nutshell, the above three functions will return a new promise packaging object. The wrapped value is the return value of the callback function. If the callback function throws an error, it wraps the rejection status promise. It seems not easy to understand, let's look at an example:

We can copy them one by one to the browser console and run them to help understanding.

​​​​​​​

// then functionPromise.resolve().then(() => 1); // The return value is new Promise(resolve => resolve(1))Promise.resolve().then(() => Promise.resolve(2)); // Return new Promise(resolve => resolve(Promise.resolve(2)))Promise.resolve().then(() => {
   
      throw new Error('abc')}); // Return new Promise(resolve => resolve(Promise.reject(new Error('abc'))))Promise.reject().then(() => 1, () => 2); // The return value is new Promise(resolve => resolve(2))
//catch functionPromise.reject().catch(() => 3); // The return value is new Promise(resolve => resolve(3))Promise.resolve().catch(() => 4); // The return value is new Promise(resolve => resolve(promise object that calls catch))//When the finally function returns a non-promise value, return the promise object before the finally function.Promise.resolve().finally(() => {}); // Return Promise.resolve()Promise.reject().finally(() => {}); // Return Promise.reject()// When the return value of the finally function is promise, wait for the returned promise to be parsed before returning the promise object before the finally function.Promise.resolve(5).finally(() => new Promise(res => {
   
      setTimeout(res, 1000);})); // Return the Promise in pending status, which will be resolved to 5 after 1 second.Promise.reject(6).finally(() => new Promise(res => {
   
      setTimeout(res, 1000);})); // Return the Promise in the pending state, and throw the number 6 after 1 second

6. What is the difference between the second callback of the then function and the catch callback?

When an error occurs in the request, the second callback function and catch of Promise's then will be triggered. There is no difference at first glance, but in fact the former cannot catch the error thrown in the current first callback function of then, but catch can.

​​​​​​​

Promise.resolve().then(   () => {
   
        throw new Error('Error from success callback');   },   () => {
   
        // will not be executed   }).catch(reason => {
   
      console.log(reason.message); // Will print out "error from success callback"});

The principle is as mentioned in the previous point. The catch function is called on the rejected Promise returned by the then function, so its errors can naturally be caught.

7. (Final) Promise implements koa2 onion middleware model

The koa2 framework introduces the onion model, which allows your requests to go in layer by layer like peeling an onion, and then come out layer by layer, thereby achieving the unification of pre- and post-request processing.

picture

Let's look at a simple koa2 onion model:

​​​​​​​

const app = new Koa();app.use(async (ctx, next) => {
   
     console.log('a-start');  await next();  console.log('a-end');});app.use(async (ctx, next) => {
   
     console.log('b-start');  await next();  console.log('b-end');});
app.listen(3000);

The above output is a-start -> b-start -> b-end -> a-end. How is such a magical output sequence achieved? Some people have no talent and simply implement it with about 20 lines of code. Any similarity is purely coincidental.

Next we analyze

Note: The following content is not friendly to novices, please read with caution.

First save the middleware function, and then call the onion model for execution after receiving the request in the listen function.

​​​​​​​

function action(koaInstance, ctx) {
   
     // ...}
class Koa {
   
      middlewares = [];   use(mid) {
   
        this.middlewares.push(mid);   }   listen(port) {
   
        // Pseudocode simulates receiving request     http.on('request', ctx => {
   
          action(this, ctx);     });   }}

After receiving the request, the pre-logic before next is executed serially starting from the first middleware.

​​​​​​​

//Start to start middleware callfunction action(koaInstance, ctx) {
   
      let nextMiddlewareIndex = 1; // Identifies the next middleware index to be executed
   //Define next function   function next() {
   
        // Before peeling the onion, calling next will call the next middleware function     const nextMiddleware = middlewares[nextMiddlewareIndex];     if (nextMiddleware) {
   
          nextMiddlewareIndex++;       nextMiddleware(ctx, next);     }   }   //Start execution from the first middleware function and pass in the ctx and next functions   middlewares[0](ctx, next);}

Process post logic after next

​​​​​​​

function action(koaInstance, ctx) {
   
      let nextMiddlewareIndex = 1;   function next() {
   
        const nextMiddleware = middlewares[nextMiddlewareIndex];     if (nextMiddleware) {
   
          nextMiddlewareIndex++;       // A return is also added here to allow the execution of the middleware function to be executed in series from back to front using promises (it is recommended to understand this return repeatedly)       return Promise.resolve(nextMiddleware(ctx, next));     } else {
   
          // When the pre-logic of the last middleware is executed, return the fullyfilled promise and start executing the post-logic after next.       return Promise.resolve();     }   }   middlewares[0](ctx, next);}

At this point, a simple onion model has been implemented.

For more exciting tutorials, please search "Qianfeng Education" on Station B

Qianfeng front-end teacher Xixiya’s HTML+CSS tutorial, a must-see video for getting started with zero-based web front-end development

Guess you like

Origin blog.csdn.net/GUDUzhongliang/article/details/135406547