The 8 advanced uses of promise during the whole meeting, and then I was asked to spray me

WeChat picture_20230804092217.jpg

Front-end interview question bank ( necessary for interview) recommendation: ★★★★★            

Address: front-end interview question bank

 

I found that many people still only know the regular usage of promise

In the js project, the use of promise should be indispensable, but I found that among colleagues and interviewers, many intermediate or above front-ends still stay in the conventional usage of , , , etc., and even they just know it, but do n’t promiseInst.then()know promiseInst.catch()it . Why.Promise.allasync/await

But in fact, there are many clever advanced usages of promise, and some advanced usages are also widely used in the alova request strategy library.

Now, I will share these with you here without reservation. After reading it, you should never be overwhelmed by the question again, and there is the finale question at the end .

If you think it is helpful to you, please like, collect and comment!

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 promiseItem();
}

If you use promise writing, then you can use the then function to connect multiple promises in series to achieve serial execution.

const requestAry = [() => api.request1(), () => api.request2(), () => api.request3()];
const finallyPromise = requestAry.reduce(
    (currentPromise, nextRequest) => currentPromise.then(() => nextRequest()),
    Promise.resolve(); // 创建一个初始promise,用于链接数组内的promise
);

2. Changing state outside the scope of new Promise

Suppose you have some functions on multiple pages that need to collect user information before allowing them to be used. Before clicking to use a function, a pop-up box for information collection pops up. How would you implement it?

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

Primary front-end : I write a modal box, and then copy and paste it to other pages, which is very efficient!

Intermediate front-end : You are not easy to maintain, we need to encapsulate this component separately, and introduce and use it on the required page!

Advanced front-end : what to seal what to pack! ! ! Wouldn't it be better to write a method call where all pages can be called?

To see how the advanced front end is implemented, take vue3 as an example to see the following example.

<!-- App.vue -->
<template>

  <!-- 以下是模态框组件 -->
  <div class="modal" v-show="visible">
    <div>
      用户姓名:<input v-model="info.name" />
    </div>
    <!-- 其他信息 -->
    <button @click="handleCancel">取消</button>
    <button @click="handleConfirm">提交</button>
  </div>

  <!-- 页面组件 -->
</template>

<script setup>
import { provide } from 'vue';

const visible = ref(false);
const info = reactive({
  name: ''
});
let resolveFn, rejectFn;

// 将信息收集函数函数传到下面
provide('getInfoByModal', () => {
  visible.value = true;
  return new Promise((resolve, reject) => {
    // 将两个函数赋值给外部,突破promise作用域
    resolveFn = resolve;
    rejectFn = reject;
  });
})

const handleConfirm = info => {
  resolveFn && resolveFn(info);
};
const handleCancel = () => {
  rejectFn && rejectFn(new Error('用户已取消'));
};
</script>

Next, call directly getInfoByModalto use the modal box to easily obtain the data filled in by the user.

<template>
  <button @click="handleClick">填写信息</button>
</template>

<script setup>
import { inject } from 'vue';

const getInfoByModal = inject('getInfoByModal');
const handleClick = async () => {
  // 调用后将显示模态框,用户点击确认后会将promise改为fullfilled状态,从而拿到用户信息
  const info = await getInfoByModal();
  await api.submitInfo(info);
}
</script>

This is also a way of encapsulating commonly used components in many UI component libraries.

3. Alternative usage of async/await

Many people only know that async函数it is used to receive the return value when calling await, but they don’t know that async函数it 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(); // 也返回一个值为1的promise对象

In awaitmost cases, the promise object is followed and waits for it to become the fullfilled state, so the fn1 function waiting below is also equivalent:

await fn1();

const promiseInst = fn1();
await promiseInst;

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

Promise.resolve().then(() => {
  console.log(1);
});
await 2;
console.log(2);
// 打印顺序位:1  2

Equivalent to

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

4. Promise implements request sharing

When a request has been sent but has not yet been responded to, the same request is initiated again, which will cause a waste of requests. At this time, we can share the response of the first request to 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 the same response value is received at the same time.

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

  1. When a page renders multiple components that internally acquire data at the same time;
  2. The submit button is not disabled, and the user clicks the submit button multiple times in succession;
  3. In the case of preloading data, the preloading page is entered before the preloading is completed;

This is also one of Alova's advanced functions. Promise's caching function is required to realize request sharing, 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) {
  // 使用请求信息作为唯一的请求key,缓存正在请求的promise对象
  // 相同key的请求将复用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;
}

5. What happens if resolve and reject are called at the same time?

Everyone knows that promises have pending/fullfilled/rejectedthree states, but in the example below, what is the final state of promise?

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

The correct answer is fullfilledstate. We just need to remember that once a promise is transferred from pendingstate to another state, it cannot be changed. Therefore, in the example, it is transferred to fullfilledstate first, and then reject()it will not be changed to rejectedstate again when it is called.

6. Thoroughly clarify the return value of then/catch/finally

To sum it up in one sentence, the above three functions will return a new promise wrapper object, the wrapped value is the return value of the executed callback function, and if the callback function throws an error, it will wrap a rejected promise. , it seems that it is not very easy to understand, let's take a look at an example:

// then函数
Promise.resolve().then(() => 1); // 返回值为 new Promise(resolve => resolve(1))
Promise.resolve().then(() => Promise.resolve(2)); // 返回 new Promise(resolve => resolve(Promise.resolve(2)))
Promise.resolve().then(() => {
  throw new Error('abc')
}); // 返回 new Promise(resolve => resolve(Promise.reject(new Error('abc'))))
Promise.reject().then(() => 1, () = 2); // 返回值为 new Promise(resolve => resolve(2))

// catch函数
Promise.reject().catch(() => 3); // 返回值为 new Promise(resolve => resolve(3))
Promise.resolve().catch(() => 4); // 返回值为 new Promise(resolve => resolve(调用catch的promise对象))

// finally函数
// 以下返回值均为 new Promise(resolve => resolve(调用finally的promise对象))
Promise.resolve().finally(() => {});
Promise.reject().finally(() => {});

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

The second callback function of promise's then and catch will both be triggered when the request fails. At first glance, there is no difference, but in fact, the former cannot catch the error thrown by the current first callback function of then, but catch can.

Promise.resolve().then(
  () => {
    throw new Error('来自成功回调的错误');
  },
  () => {
    // 不会被执行
  }
).catch(reason => {
  console.log(reason.message); // 将打印出"来自成功回调的错误"
});

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

8. (Finale) Promise implements koa2 onion middleware model

The koa2 framework introduces the onion model, which allows your request to be like peeling an onion, entering layer by layer and then reverse layer by layer, so as to achieve unified pre- and post-processing of requests.

image.png

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  a-start -> b-start -> b-end -> a-endis how to achieve such a magical output sequence. Someone who is not talented used about 20 lines of code to realize it simply. If it is similar to koa, it is purely coincidental.

Next we analyze

Note: The following content is not friendly to novices, please watch carefully.

  1. First save the middleware function first, and call the execution of the onion model after receiving the request in the listen function.
function action(koaInstance, ctx) {
  // ...
}

class Koa {
  middlewares = [];
  use(mid) {
    this.middlewares.push(mid);
  }
  listen(port) {
    // 伪代码模拟接收请求
    http.on('request', ctx => {
      action(this, ctx);
    });
  }
}
  1. After receiving the request, start from the first middleware to serially execute the preceding logic before next.
// 开始启动中间件调用
function action(koaInstance, ctx) {
  let nextMiddlewareIndex = 1; // 标识下一个执行的中间件索引
  
  // 定义next函数
  function next() {
    // 剥洋葱前,调用next则调用下一个中间件函数
    const nextMiddleware = middlewares[nextMiddlewareIndex];
    if (nextMiddleware) {
      nextMiddlewareIndex++;
      nextMiddleware(ctx, next);
    }
  }
  // 从第一个中间件函数开始执行,并将ctx和next函数传入
  middlewares[0](ctx, next);
}
  1. Post logic after processing next
function action(koaInstance, ctx) {
  let nextMiddlewareIndex = 1;
  function next() {
    const nextMiddleware = middlewares[nextMiddlewareIndex];
    if (nextMiddleware) {
      nextMiddlewareIndex++;
      // 这边也添加了return,让中间件函数的执行用promise从后到前串联执行(这个return建议反复理解)
      return Promise.resolve(nextMiddleware(ctx, next));
    } else {
      // 当最后一个中间件的前置逻辑执行完后,返回fullfilled的promise开始执行next后的后置逻辑
      return Promise.resolve();
    }
  }
  middlewares[0](ctx, next);
}

So far, a simple onion model has been realized.

end

Readers, if you think it is useful to you, please give it a thumbs up or bookmark, and is there any more advanced usage that you don't understand? Please express your thoughts in the comment area!

Front-end interview question bank ( necessary for interview) recommendation: ★★★★★            

Address: front-end interview question bank

Guess you like

Origin blog.csdn.net/weixin_42981560/article/details/132109817