掌握异步编程,看这里!

大家好,我是拾七,持续给大家补给前端干货🔥

异步处理的概念

JavaScript 中的异步处理指的是在代码执行过程中,能够不阻塞当前线程并处理一些时间较长的操作。异步处理通常涉及到回调函数、Promise、async/await 等机制。

在 JavaScript 中,传统的同步处理方式采用的是阻塞式的单线程模型。这种模型的缺点是当一个任务被执行时,它会一直执行到结束,期间如果有耗时的操作也会一直阻塞下去,直到任务执行完毕,才会执行后续的任务。这种方式会导致页面卡死,体验非常不好。

因此,JavaScript 异步处理机制应运而生,它允许在代码执行过程中,执行一些耗时的操作,而不会阻塞当前线程。

回调函数

回调函数是一种很常见的异步编程模型,通过在异步操作完成后调用回调函数来通知异步操作已结束,从而执行后续的任务。例如:

javascript 复制代码
function fetchData(callback) {  setTimeout(function() {const data = { name: '张三', age: 20 };    callback(data);  }, 1000);}​fetchData(function(data) {  console.log(data);});

在这个示例中,fetchData() 函数在完成数据加载后,调用回调函数 callback() 并传递数据作为参数。当数据加载完成后,控制器会跳转到回调函数中执行后续任务。

Promise

Promise 是一种比较流行的异步编程模型,它可以在异步操作完成后执行一些回调操作,并将结果返回给请求方。Promise 代表了一个异步操作的最终完成(或失败)及其结果值。例如:

javascript 复制代码
function fetchData() {  return new Promise(function(resolve, reject) {    setTimeout(function() {      const data = { name: '张三', age: 20 };      resolve(data);    }, 1000);  });}​fetchData().then(function(data) {  console.log(data);});

异步处理常见场景与处理策略

异步处理常见场景包括但不限于:

  1. 网络请求:当请求数据需要一定的时间才能返回时,为了避免用户体验受到影响,需要进行异步处理。
  2. 定时任务:定时执行任务,需要进行异步处理。
  3. 事件处理:通过异步处理来避免事件处理函数执行时间过长,导致页面卡顿等问题。
  4. 大量数据处理:对于大量数据的处理,需要进行异步处理,以免阻塞主线程。

JavaScript 引擎是单线程执行的,也就是说同一时间内只有一个任务在执行。当需要进行异步操作时,通常会使用回调函数。

假设我们有一个获取用户信息的异步函数 getUserInfo,在信息获取完成后需要调用相关回调函数。一种实现方式是将回调函数作为 getUserInfo 函数的第二个参数传入,信息获取完成后调用该函数。

javascript 复制代码
function getUserInfo(userId, callback) {  setTimeout(function() {    const userInfo = {      id: userId,      name: "Tom",      age: 25    }    callback(userInfo)  }, 1000)}​getUserInfo(1001, function(userInfo) {  console.log(userInfo)})

上述代码首先调用 getUserInfo 函数,该函数通过 setTimeout 模拟异步操作,等待 1 秒钟后获取用户信息,并在信息获取完成后调用传入的回调函数。最后在回调函数中输出用户信息。

Promise A+ 规范

Promise 的状态

一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending) 、执行态(Fulfilled)和 拒绝态(Rejected) 。

  • 等待态(Pending) 处于等待态时,promise 需满足以下条件:
    • 可以迁移至执行态或拒绝态(fulfill、reject)
  • 执行态(Fulfilled) 处于执行态时,promise 需满足以下条件:
    • 不能迁移至其他任何状态
    • 必须拥有一个不可变的终值
  • 拒绝态(Rejected) 处于拒绝态时,promise 需满足以下条件:
    • 不能迁移至其他任何状态

    • 必须拥有一个不可变的原因

    • 这里的不可变指的是恒等(即可用 === 判断相等),而不是意味着更深层次的不可变(译者注:概指当 value 或 reason 不是基本值时,只要求其引用地址相等,但属性值可被更改)。

面试官问到 Promise A+ 规范时,首先要提出的便是三个态:pending、fulfilled、rejected


一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。

promise 的 then 方法接受两个参数:

arduino 复制代码
promise.then(onFulfilled, onRejected);

其中,onFulfilled 和 onRejected 都是可选参数。

  • 如果 onFulfilled 不是函数,其必须被忽略

  • 如果 onRejected 不是函数,其必须被忽略

发布-订阅模式

根据 Promise A+ 规范,每次 then 返回的值也需要满足 thenable,也就是说我们需要将 resolve 返回值使用 promise 包裹,在本例中就是需要将返回值包装为新的 HePromise 对象。开发之前我们不妨先来看看 Promise 链式调用的示例:

javascript 复制代码
const p = new Promise(resolve => resolve(1));p.then(r1 => {console.log(r1);return 2;})  .then(r2 => {console.log(r2);return 3;  })  .then(r3 => {console.log(r3);  });​

实现all方法

就是将传入数组中的值 promise 化,然后保证每个任务都处理后,最终 resolve。示例如下:

typescript 复制代码
class HePromise {static all(promises: any[]) {let index = 0;const result: any[] = [];const pLen = promises.length;return new HePromise((resolve, reject) => {      promises.forEach(p => {        HePromise.resolve(p).then(val => {            index++;            result.push(val);if (index === pLen) {              resolve(result);            }          },err => {if (reject) reject(err);          },        );      });    });  }}

编写测试用例如下:

scss 复制代码
it('HePromise.all', done => {  HePromise.all([1, 2, 3]).then(res => {    expect(res).toEqual([1, 2, 3]);    done();  });});

执行测试,测试通过。

实现race方法

就是将传入数组中的值 promise 化,只要其中一个任务完成,即可 resolve。示例如下:

scss 复制代码
class HePromise {static race(promises: any[]): HePromise {return new HePromise((resolve, reject) => {      promises.forEach(p => {        HePromise.resolve(p).then(val => {            resolve(val);          },err => {if (reject) reject(err);          },        );      });    });  }}

编写测试用例:

scss 复制代码
it('HePromise.race', done => {  HePromise.race([11, 22, 33]).then(res => {    expect(res).toBe(11);    done();  });});

执行测试,测试通过。

整体测试代码情况如下:

async 与 await 用法及原理详解

javascript 复制代码
async function test() {    const res = await Promise.resolve(1)    return res}

需要注意的是,使用 async、await 处理异步操作时,需要注意异常的处理。

异常处理

通常我们使用 try、catch 捕获 async、await 执行过程中抛出的异常,就像这样:

javascript 复制代码
async function test() {    let res = null    try {        const res = await Promise.resolve(1)        return res    } catch(e) {        console.log(e)    }}

从零实现一个类似 async、await 的函数

promise+generator

javascript 复制代码
function fn(nums) {    return new Promise(resolve = >{        setTimeout(() = >{            resolve(nums * 2)        },        1000)    })}function * gen() {    const num1 = yield fn(1)    const num2 = yield fn(num1)    const num3 = yield fn(num2)    return num3}function generatorToAsync(generatorFn) {    return function() {        return new Promise((resolve, reject) = >{            const g = generatorFn() const next1 = g.next() next1.value.then(res1 = >{​                const next2 = g.next(res1) // 传入上次的res1                next2.value.then(res2 = >{​                    const next3 = g.next(res2) // 传入上次的res2                    next3.value.then(res3 = >{​                        // 传入上次的res3                        resolve(g.next(res3).value)                    })                })            })        })    }}​const asyncFn = generatorToAsync(gen)​asyncFn().then(res = >console.log(res)) // 3秒后输出 8​

自动执行

自动执行其实就是运用递归,将生成器函数产生的数据不断调用 next,直至执行完成。

lua 复制代码
function getData(endpoint) {  return new Promise(resolve => {    setTimeout(() => {      resolve(`Data received from ${endpoint}`)    }, 2000)  })}​// 生成器函数function* getDataAsync() {  const result1 = yield getData('Endpoint 1')  console.log(result1)  const result2 = yield getData('Endpoint 2')  console.log(result2)  return 'All data received'}​// 将生成器函数包装成 Promisefunction asyncToPromise(generatorFn) {  const generator = generatorFn()​  function handleResult(result) {    if (result.done) {      return Promise.resolve(result.value)    }​    return Promise.resolve(result.value)      .then(res => handleResult(generator.next(res)))      .catch(err => handleResult(generator.throw(err)))  }​  try {    return handleResult(generator.next())  } catch (error) {    return Promise.reject(error)  }}​​asyncToPromise(getDataAsync).then(result => console.log(result))​

总结

这里介绍了回调函数、Promise、async/await 等机制。其中涉及到JavaScript事件循环机制没有展开分析,后续会总结相应的通关手册。

相关推荐
你挚爱的强哥41 分钟前
✅✅✅【Vue.js】sd.js基于jQuery Ajax最新原生完整版for凯哥API版本
javascript·vue.js·jquery
y先森1 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy1 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189111 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿2 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡3 小时前
commitlint校验git提交信息
前端
虾球xz4 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇4 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒4 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员4 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js