JavaScript复习-异步

2021-05-12 10:36:562024-04-07 19:47:28

JavaScript为异步非阻塞,ES6之前主要靠回调函数来实现异步,但由于回调函数容易出现回调地狱等问题,于是ES6+提供了了GeneratorPromiseasync/await这些API来降低异步编程的难度与复杂度。

回调函数

回调函数容易出现回调地狱,可读性差

function ajax(url, options, onSuccess) {
    // ...
}

ajax('/api/xxx', {}, function () {
    ajax('/api/yyy', {}, function () {
        ajax('/api/zzz', (), function () {
        })
    })
})

Generator

形式上,generator函数是一个状态机,封装了多个内部状态。执行generator函数会返回一个遍历器对象

语法上,generator函数是一个普通函数。function关键字与函数名之间有一个*;函数体内使用yield表达式定义不同的内部状态

与普通函数不同,generator函数被调用后并不执行,返回的是一个指向内部状态的指针对象,也就是遍历器对象。下一步,必须调用遍历器对象的next方法使得指针移向下一个状态,也就是说每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yieldreturn

function* gen() {
  const str1 = yield "hello"
  // str1 = 222
  const str2 = yield "world"
  // str2 = 333
  return str2
}

const iter = gen()
console.log(iter.next())
// { value: "hello", done: false }
console.log(iter.next(222))
// { value: "world", done: false }
console.log(iter.next(333))
// { value: 333, done: false }

第一次调用,generator函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value值就是当前yield表达式的值,done的值为false

第二次调用,generator从上次yield表达式停下来的地方一直执行到下一个yield表达式 👆 ……

第三次调用,generator函数从上次yield表达式停下来的地方一直执行到return语句,next返回的value值就是return后面表达式的值,donetrue

yield表达式本身没有返回值(或者说总是返回undefined)。next方法可以带一个参数,该参数会被当作上一个yield表达式的返回值

Promise

ES6提供的一种异步编程方案

一个Promise对象必然处于以下三种状态:

  • pending,初始状态
  • fulfilled,成功
  • rejected,失败

pending状态的Promise对象可以转换为fulfilledrejected状态,而且此过程是不可逆的。当状态变更时,then()方法注册的回调就会被调用

then() 或者 catch() 的参数期望是函数,传⼊⾮函数则会发⽣值透传(value => value)

Promise 的异步特性: 它们是同步对象(在同步执行模式中使用),但也是异步执行模式的媒介。

Promise.resolve(1)
 .then(2) // .then(1 => 1)
 .then(Promise.resolve(3)) // .then(1 => 1)
 .then(console.log)
// 1
function fetch(url, options = {}) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(options.method || 'GET', url);
    const headers = { 'Content-Type': 'application/json', ...options.headers || {} };
    Reflect.ownKeys(headers)
      .forEach((header) => xhr.setRequestHeader(header, headers[header]));
    xhr.send(options.method === 'POST' ? JSON.stringify(options.data || {}) : null);
    xhr.onerror = (e) => reject(e);
    xhr.onreadystatechange = () => {
      if (xhr.readyState === XMLHttpRequest.DONE
        && [200, 201, 301, 302, 304].includes(xhr.status)) {
        resolve(JSON.parse(xhr.responseText));
      }
    };
  });
}
fetch('http://localhost:3004/get').then((res) => {
    console.log(res);
}).catch((e) => {
    console.error(e);
})
new Promise((resolve,reject)=>{
    console.log("1")
    resolve();
}).then(()=>{
    // 外部第1个then
    console.log("2")
    new Promise((resolve,reject)=>{
        console.log("3")
        resolve();
    }).then(()=>{
        // 内部第1个then
        console.log("4")
    }).then(() => {
        // 内部第2个then
        console.log("5")
    })
}).then((res)=>{
    // 外部第2个then
    console.log("6")
})

简单来讲就是 then 回调的注册需要上一个 then 里面的同步代码执行完毕

拿上面的代码来讲,当外部第1个 then 里的 resolve() 执行完毕后,该 Promise 的状态已经更改,会将内部第1个 then 回调添加(注册)到微任务队列中;内部第2个 then 由于上一个 then 回调没有执行完毕,因此不会注册。此时外部第1个 then 里的同步代码执行完毕,会注册外部第2个 then 回调

整理一下:then回调注册的顺序是:外部第1个then --> 内部第1个then --> 外部第2个then --> 内部第2个then

ps: 如果将外部第1个then里的new Promise(xxx)改为return new Promise(xxx)的话内部第2个then的注册将早于* 外部第2个then*

async/await

目前来讲,最为优秀的一种异步编程方案,与ES2017提出

实现上是Generator+Promise的语法糖

通过在onFulFilled()里调用next()、在next()里调用onFulfilled()形成一个自执行器,只有当全部代码执行完毕后才会终止

async function fn() {
    try {
        const res = await fetch('http://localhost:3004/get');
        console.log(res);
    } catch (e) {
        console.error(e);
    }
}

async/await原理: 自动执行generator函数

const getData = () =>
  new Promise((resolve) => setTimeout(() => resolve("data"), 1000));

function* testG() {
  // await被编译成了yield
  const data = yield getData();
  console.log("data: ", data);
  const data2 = yield getData();
  console.log("data2: ", data2);
  return "success";
}

function asyncToGenerator(generatorFunc) {
  return function (...args) {
    const gen = generatorFunc.apply(this, args);

    return new Promise((resolve, reject) => {
      function step(key, arg) {
        let generatorResult;
        try {
          generatorResult = gen[key](arg);
        } catch (error) {
          return reject(error);
        }

        const { value, done } = generatorResult;

        if (done) {
          return resolve(value);
        } else {
          return Promise.resolve(value).then(
            function onResolve(val) {
              step("next", val);
            },
            function onReject(err) {
              step("throw", err);
            }
          );
        }
      }
      step("next");
    });
  };
}

const testGAsync = asyncToGenerator(testG);
testGAsync().then((result) => {
  console.log(result);
});

参考

MDN-Promise

深度揭秘 Promise 微任务注册和执行过程

co-share