JavaScript异步

非 Y 不嫁゛ 提交于 2019-11-29 00:46:53

 JavaScript异步类型

  • 延迟类型:setTimeout、setInterval、setImmediate
  • 监听事件:监听new Image加载状态、监听script加载状态、监听iframe加载状态、Message
  • 带有异步功能类型: Promise、ajax、Worker、async/await

JavaScript常用异步编程

Promise

  Promise有几个特点:

  1. 对象的状态不受外界影响,有三种状态:pending(进行中)、fulfilled(成功)、rejected(失败)。只有异步操作的结果可以决定当前是哪种状态,其他操作无法改变。
  2. 状态一旦改变,就不会再变,任何时候都可以得到这个结果。状态改变只可能是:pending -> fulfilled 或 pending -> rejected
  3. 实例化后,会立即执行一次。所以一般将其用函数包裹起来,使用的时候调用一次。
  4. 如果执行后的回调也要做一些异步操作,可以无限的.then下去,当然要保证有返回值

  方法:

  • 原型方法 all race reject resolve
  • 对象方法 then catch finally(ES9)
function promiseTest(n,msg) {
   return new Promise((resolve,reject)=>{
      setTimeout(function () {
         console.log(`执行第${n}个任务`);
         msg.code && resolve(msg.text);  // 当认为成功的时候,调用resolve函数
         !msg.code && reject(msg.text);   // 当认为失败的时候,调用reject函数
      },n*500)
   });
}
let pro = promiseTest(1,{code:true,text:"返回的数据1"});
/* 没有catch,每个then里两个回调函数,此时第一个为成功的回调,第二个为失败的回调 */
pro.then((data)=>{
   console.log(data); // 执行成功结果在这里
   // return promiseTest(2,{code:true,text:"返回的数据2"});
   return promiseTest(2,{code:false,text:"失败的数据"});
},(err)=>{
   console.log(err); // 执行失败的结果在这里
}).then((data)=>{console.log(data)},(err)=>{console.log(err)});

观察then和catch的用法:

  • 在多次then后最后跟一个catch,可以捕获所有的异常
/* 多个then和一个catch */
pro.then((data)=>{
   console.log(data);
   return promiseTest(2,{code:false,text:"失败的数据"});
}).then((data)=>{
   console.log(data)
}).catch((err,data)=>{
   console.log("失败了",err);
});

all和rece的用法:

  • all接收一个promise对象列表.在所有异步操作执行完且全部成功的时候才执行then回调,只要有一个失败,就执行catch回调(只对第一个失败的promise对象执行)。
  • race也接收一个promise对象列表,不同的是,哪个最先执行完,对应的那个对象就执行then或catch方法(then或catch只执行一次)。
/* all的用法 */
Promise.all([
   promiseTest(1,{code:true,text:"返回的数据1"}),
   promiseTest(2,{code:false,text:"返回的数据2"}),
   promiseTest(3,{code:false,text:"返回的数据3"})
]).then((res)=>{console.log("全部成功",res)}).catch((err)=>{console.log("失败",err);});

/* race的用法 */
Promise.race([
   promiseTest(1,{code:false,text:"返回的数据1"}),
   promiseTest(2,{code:false,text:"返回的数据2"}),
   promiseTest(3,{code:true,text:"返回的数据3"})
]).then((res)=>{console.log("成功",res)}).catch((err)=>{console.log("失败",err);});

Generator

Generator是什么?叫做生成器,通过function*关键字来定义的函数称之为生成器函数(generator function),与Python的语法一模一样

生成器有3个方法,都有一样的返回值{value,done}

  • .next(value)   value为向生成器传递的值。
  • .return(value) value为需要返回的值。该方法返回给定的值并结束生成器。
  • .throw(exception)  exception用于抛出的异常。该方法用来向生成器抛出异常,并恢复生成器的执行。

生成器的作用:

可以和 Promise 组合使用。减少代码量,写起来更方便。在没有Generator时,写Promise会需要很多的then,每个then内都有不同的处理逻辑。现在,我们将所有的逻辑写进一个生成器函数(或者在生成器函数内用yield 进行函数调用),Promise的每个then内调用同一个函数即可。

定义生成器:

function add(a,b) {
   console.log("+");
   return a+b;
}
function cut(a,b) {
   console.log("-");
   return a-b;
}
function mul(a,b) {
   console.log("*");
   return a*b;
}
function division(a,b) {
   console.log("/");
   return a/b;
}
function* compute(a, b) {
  yield add(a,b);
  yield cut(a,b);
  let value = yield mul(a,b);
  console.log("value",value); // 第三次调用.next()时无法为value赋值,需要第四次调用才能为其赋值
  yield mul(a,b);
  yield division(a,b);
}

使用生成器:

// 执行一下这个函数得到 Generator 实例,调用next()方法执行,遇到yield暂停
let generator = compute(4, 2);

function promise() {
   return new Promise((resolve, reject) => {
      let res = generator.next();
      if(res.value > 5)
      {
         resolve("OK");
      }else
      {
         reject("小于5")
      }
   });
}

let proObj = promise();
proObj.then((data)=>{
   console.log(data);
   let res = generator.next();
   console.log("Promise res1",res);
}).then((data)=>{
   let res = generator.next();
   // let res = generator.return();
   console.log("Promise res2",res);
}).then((data)=>{
   let res = generator.next("qwe"); // 第四次next()时,向生成器传数据
   console.log("Promise res3",res)
}).catch((err)=>{
   console.log("出错",err);
});

async/await

优点:简洁,节约了不少代码

  • 被async修饰的函数,总会返回一个Promise对象。如果代码中返回值不是promise对象,它会将其包装成promise对象的resolved值
  • await只能在async函数内使用。它是一个操作符,等待一个函数或表达式。经过该操作符处理后,输出一个值。

如果在异步函数中,每个任务都需要上个任务的返回结果,可以这么做:

function takeLongTime(n) {
   return new Promise((resolve,reject) => {
      setTimeout(() => {resolve(n + 200)}, n);
   });
}

function step1(n) {
   console.log(`step1 with ${n}`);
   return takeLongTime(n);
}

function step2(m, n) {
   console.log(`step2 with ${m} and ${n}`);
   return takeLongTime(m + n);
}

function step3(k, m, n) {
   console.log(`step3 with ${k}, ${m} and ${n}`);
   return takeLongTime(k + m + n);
}


async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time1, time2);
    const result = await step3(time1, time2, time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}
doIt();

如果这几个任务没有关联,可以这样做:

async function doIt() {  // 函数执行耗时2100ms
   console.time("doIt");
   await step1(300).catch((err)=>{console.log(err)});  // 异常处理
   await step1(800);
   await step1(1000);
   console.timeEnd("doIt");
}
doIt();

当然,最好这样做:

async function doIt() { // 函数执行耗时1000ms
   console.time("doIt");
   const time1Pro = step1(300);
   const time2Pro = step1(800);
   const time3Pro = step1(1000);
   await time1Pro;
   await time2Pro;
   await time3Pro;
   console.timeEnd("doIt");
}
doIt();

注意:

  1. async/await并没有脱离Promise,它的出现能够更好地协同Promise工作。
    • 怎么体现更好地协同?它替代了then catch的写法。使得等待promise值的操作更优雅,更容易阅读和书写。
  2. 函数仅仅加上async并没有意义,它仍然是同步函数,只有与await结合使用,它才会变成异步函数。
    • 这需要精准理解await。它在等待的时候并没有阻塞程序,此函数也不占用CPU资源,使得整个函数做到了异步执行。
  3. doIt()函数内部是串行执行的,但它本身是异步函数。
  4. 在这个异步函数内,可能会做很多操作ABC,他们有执行的先后顺序。这时你可能会想,A、B、C之间没有关联,他们之间可以是并行执行的,并不需要串行,那怎么办?
    • 【错误想法】这样想没错,但是没必要。因为他们已经存在于异步函数内了,所有的操作已经是异步的。在同样的环境情景下,底层执行的效率是相同的,并不见得因为A和B之间互相异步而提高效率。
    • 【正确想法】这样想是有必要的。参照两个doIt(),调用的函数返回promise对象,前者是依次生成promise对象(依次执行任务),依次等待返回结果。等待总时长取决于所有任务执行时间之和。后者则是同时生成promise对象(同时执行任务),依次等待。等待总时长取决于耗时最长的任务。后者的CPU运用率更高。
  5. 错误处理。最标准的方法是使用try...catch语句,但是它不仅会捕捉到promise的异常,还会将所有出现的异常捕获。因此,可以使用.catch,只会捕获promise相关的异常。

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!