JavaScript中 处理异步的几种方法

简介: JavaScript中 处理异步的几种方法

1. 回调函数

回调(callback)是一个函数被作为一个参数传递到另一个函数里,在那个函数执行完后再执行。

假定有两个函数f1和f2,f2等待f1的执行结果,f1()–>f2();如果f1很耗时,可以改写f1,把f2(箭头函数)写成f1的回调函数:

function f1(callback){
  setTimeout(() => {
    let name = '小明'
    console.log('大家好') // f1的任务代码
    callback(name)
  }, 100);
}
f1((name)=>{
  console.log('我是'+name)
})
// 大家好
// 我是小明

优点:简单、方便、实用。

缺点:易形成回调函数地狱。如果我们只有一个异步操作,用回调函数来处理是完全没有任何问题的。。但是如果我们要嵌套很多个回调函数,问题就很大了,因为多个异步操作形成了强耦合,代码将乱作一团,无法管理。这种情况被称为"回调函数地狱"(callback hell)。

getData('XXX1', () => {
  // callback 函数体
  getData('XXX2', () => {
    // callback 函数体
    getData('XXX3', () => {
      // callback 函数体
      getData('XXX4', () => {
        // callback 函数体
        getData('XXX5', () => {
          // callback 函数体
        })
      })
    })
  })
})

2. Promise 对象(推荐)

含义: Promise是异步编程的一种解决方案,

优点: Promise是链式编程,有效的解决了令人头痛的回调地狱问题,Promise的结果有成功和失败两种状态,只有异步操作的结果,可以决定当前是哪一种状态,外界的任何操作都无法改变这个状态。

2.1 常用API

resolve 返回异步操作成功的结果

reject 返回异步操作失败的结果

then 执行 Promise 状态是成功的操作

catch 执行 Promise 状态是失败的操作

finally 不管 Promise 状态是成功或失败都执行的操作

//ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
    const p = new Promise(function(resolve,reject){
        if(success){
            resolve('成功的结果')
        }else{
            reject('失败的结果')
        }
    })
    p.then(function (res) {
        // 接收resolve传来的数据,做些什么
    },function (err) {
        // 接收reject传来的数据,做些什么
    })
    p.catch(function (err) {
        // 接收reject传来的数据或者捕捉到then()中的运行报错时,做些什么
    })
    p.finally(function(){
        // 不管什么状态都执行
    })
const p = new Promise((resolve, reject) =>{
  setTimeout(() => {
    let name = '小明'
    resolve(name)
  }, 100);
});
p.then((res) => {
  console.log(res)
})

2.2 Promise.all

Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3])

p 的状态由 p1、p2、p3 决定,分成两种情况。

(1)只有 p1、p2、p3 的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数(then)。

(2)只要 p1、p2、p3 之中有一个被 rejected,p 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数(catch)。

优点:

promise.all 将多个 promise 放在一起处理,能简化回调的处理(不然需要调很多次,不科学),一个 then 回调就能拿到所有数据,对其进行处理,也能用一个 catch 回调捕获所有的异常。

console.time('time');
function test1(){
  const p1 =  new Promise((resolve, reject) => {
    console.log('begin p1');
    setTimeout(() => {
      resolve('end p1');
    },400);
  });
  return p1;
}
function test2(){
  const p2 = new Promise((resolve, reject) => {
    console.log('begin p2');
    setTimeout(() => {
      resolve('end p2');
    },300);
  });
  console.log(p2);
  return p2;
}
function load1(){
  setTimeout(() => {
    console.log('setTimeout');
    Promise.all([test1(), test2()]).then((res) => {
      console.log('load1',res);
      console.timeEnd('time');
    }).catch(err => {
      console.log(err)
    });
  },100);
}
load1();
console.log('mark');

2020062310470442.png

console.time('time');
function test1(){
  const p1 =  new Promise((resolve, reject) => {
    console.log('begin p1');
    setTimeout(() => {
      resolve('end p1');
    },400);
  });
  return p1;
}
function test2(){
  const p2 = new Promise((resolve, reject) => {
    console.log('begin p2');
    setTimeout(() => {
      resolve('end p2');
    },300);
  });
  console.log(p2);
  return p2;
}
async function load2(){
  const r1 = await test1();
  const r2 = await test2();
  console.log('load2',r1,r2);
  console.timeEnd('time');
}
load2();
console.log('mark');

2020062310470442.png

3. 事件监听

采用事件驱动模式,任务的执行不取决代码的顺序,而取决于某一个事件是否发生。

监听函数有:on,bind,listen,addEventListener,observe

举例,为f1绑定一个事件(jquery写法):f1.on(‘done’,f2);即当f1发生done事件,就执行f2。

function f1(){
  settimeout(function(){
    // f1的任务代码
    f1.trigger('done');  // 执行完成后,立即触发done事件,从而开始执行f2
  },1000);
}

优点:易理解,可绑定多个事件,每一个事件可指定多个回调函数,可以去耦合,有利于实现模块化

缺点:整个程序都要变成事件驱动型,运行流程会变得不清晰

4. Generator 函数

含义: Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

基本用法:

    function* helloGenerator() {
      yield 'hello';
      yield 'Generator';
      return 'over';
    }
    let hw = helloGenerator();
    hw.next() // {value:"hello",done:false}
    hw.next() // {value:"Generator",done:false}
    hw.next() // {value:"over",done:true}
    hw.next() // {value:undfined,done:true}

特征

function关键字与函数名之间有一个星号;

函数体内部使用yield表达式,定义不同的内部状态;

通过next方法获取每段状态的返回结果,上面分成4次执行了Gennrator函数。

第一次,获取第一个yield函数的返回结果并暂停,done:false,代表函数内的状态还没有执行结束;

第二次,同理;

第三次,获取return 的返回结果,done:true表示函数内的状态已经执行完毕;

第四次,函数内已经没有可以执行的状态,所有返回undfined,同时告诉函数内的状态已经执行完毕;

如果函数内没有return,在第三次时返回undfined,done:true表示函数内的状态已经执行完毕;

5. async 函数 (推荐)

含义: async 函数是在ES2017 标准中引入的,async 使得异步操作变得更加方便,其实他就是Generator 函数的语法糖

基本用法:

function get1(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve(1)
    },2000)      
  })
}
async function getSet(){
  const n = await get1()
  //const n = await '111'
  return n
}
getSet().then(console.log)

说明:

await后面是一个Promise对象,如get1 return出去的Promise实例;

如果不是 Promise 对象,就直接返回对应的值,如直接返回’111’。

若Promise 对象, 并且其以值 x 被 fulfilled, 则返回值为 x.

Promise 对象, 并且其以异常 e 被 rejected, 则抛出异常 e

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态 改变;

如果任何一个await语句后面的 Promise 对象变为reject状态或遇到return,那么整个async函数都会中断执行。

await 在等待 Promise 对象时会导致 async function 暂停执行, 一直到 Promise 对象决议之后才会 async function 继续执行;

如果我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在 try…catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}
f().then(v => console.log(v))
function getData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let name = '明明'
      resolve(name)
    }, 100);
  });
}
async function test() {
  let newData = await getData()
  console.log(newData)
}
test()
// 明明

优点: 相比Generator函数,async函数有如下四点改进

内置执行器: Generator 函数的执行必须靠next()进行每一次的模块执行,async自带执行器,只需要和普通函数一样调用即可执行

更好的语义:async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

返回值是Promise: async函数的返回值是 Promise 对象,可以用then方法指定下一步的操作;

而且async函数完全可以看做多个异步函数的操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖,即Promise.all()的用法 ;

多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

    //此处省略getFoo(), getBar()两个函数
    // 写法一
    async function getSet(){
        let [foo, bar] = await Promise.all([getFoo(), getBar()]);
        return [foo, bar]
    }
    // 写法二
    async function getSet(){
        let fooPromise = getFoo();
        let barPromise = getBar();
        let foo = await fooPromise;
        let bar = await barPromise;
        return [foo, bar]
    }

参考文章(侵删)


相关文章
|
1月前
|
JavaScript 前端开发 程序员
前端原生Js批量修改页面元素属性的2个方法
原生 Js 的 getElementsByClassName 和 querySelectorAll 都能获取批量的页面元素,但是它们之间有些细微的差别,稍不注意,就很容易弄错!
|
1月前
|
Web App开发 JavaScript 前端开发
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
|
1月前
|
监控 JavaScript Java
Node.js中内存泄漏的检测方法
检测内存泄漏需要综合运用多种方法,并结合实际的应用场景和代码特点进行分析。及时发现和解决内存泄漏问题,可以提高应用的稳定性和性能,避免潜在的风险和故障。同时,不断学习和掌握内存管理的知识,也是有效预防内存泄漏的重要途径。
135 52
|
1月前
|
JSON 前端开发 JavaScript
在 JavaScript 中,如何使用 Promise 处理异步操作?
通过以上方式,可以使用Promise来有效地处理各种异步操作,使异步代码更加清晰、易读和易于维护,避免了回调地狱的问题,提高了代码的质量和可维护性。
|
1月前
|
JavaScript 前端开发 索引
js中DOM的基础方法
【10月更文挑战第31天】这些DOM基础方法是操作网页文档结构和实现交互效果的重要工具,通过它们可以动态地改变页面的内容、样式和行为,为用户提供丰富的交互体验。
|
1月前
|
缓存 JavaScript UED
js中BOM中的方法
【10月更文挑战第31天】
|
1月前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
50 5
|
1月前
|
JavaScript 前端开发
js中的bind,call,apply方法的区别以及用法
JavaScript中,`bind`、`call`和`apply`均可改变函数的`this`指向并传递参数。其中,`bind`返回一个新函数,不立即执行;`call`和`apply`则立即执行,且`apply`的参数以数组形式传递。三者在改变`this`指向及传参上功能相似,但在执行时机和参数传递方式上有所区别。
29 1
|
1月前
|
JavaScript 前端开发
.js方法参数argument
【10月更文挑战第26天】`arguments` 对象为JavaScript函数提供了一种灵活处理参数的方式,能够满足各种不同的参数传递和处理需求,在实际开发中具有广泛的应用价值。
46 7
|
1月前
|
JavaScript 前端开发 图形学
JavaScript 中 Math 对象常用方法
【10月更文挑战第29天】JavaScript中的Math对象提供了丰富多样的数学方法,涵盖了基本数学运算、幂运算、开方、随机数生成、极值获取以及三角函数等多个方面,为各种数学相关的计算和处理提供了强大的支持,是JavaScript编程中不可或缺的一部分。