JavaScript 异步简单原理 promise、async、await应用

异步

  • 异步 是与同步 相对的概念。
    • 同步:指在 主线程 上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。也就是调用一旦开始,必须这个调用 返回结果才能继续往后执行。程序的执行顺序和任务排列顺序是一致的。
    • 异步
      • 是指不进入主线程,而进入 任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
      • 每一个任务有一个或多个 回调函数。前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行。
      • 程序的执行顺序和任务的排列顺序是不一致的,异步的。
      • setTimeout、setInterval函数、Ajax都是异步操作。
  • 异步就是从主线程发射一个子线程来完成任务。
  • 同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。

出现原因

js是单线程,为了不浪费时间,提高运行效率。

异步工作原理

主要是依据settimeout(定时执行回调函数)和setinterval(定时执行回调函数并以相同间隔重复执行,可以通过clearInterval取消重复)函数。

js有一个专门处理计时器的模块。js主线程先解决同步任务,与此同时,计时器模块独立运行,将到时的任务放入异步队列,当同步任务解决完以后,js会解决到时的异步队列。

实现异步的方法

JS 异步编程进化史:callback -> promise -> generator -> async + await

  • 回调函数
  • 事件监听:
    • 优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合",有利于实现模块化。
    • 缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。
  • 发布订阅:性质与"事件监听"类似,但是明显优于后者。因为可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
  • Promise/A+
  • 生成器Generators/ yield
    • 语法上可以理解为Generator 函数是一个状态机,封装了多个内部状态。
    • Generator还是一个遍历器对象生成函数。可以使用yield暂停函数, 可暂停,next方法可启动,每次返回的是yield后的表达式结果。
    • yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
  • async/await

回调函数

回调函数就是一个函数,它是在我们启动一个异步任务的时候就告诉它:等你完成了这个任务之后要干什么。这样一来主线程几乎不用关心异步任务的状态了,他自己会善始善终。

复制代码
ajax(url, () => {
    // 处理逻辑
    ajax(url1, () => {
        // 处理逻辑
        ajax(url2, () => {
            // 处理逻辑
        })
    })
})

使用原因

js遇到回调函数即会放入异步队列。
回调正是确保一段代码执行完毕之后再执行另一段代码的方式 ,可以确保函数异步执行。

回调函数优点:不会立即执行、是个闭包(可以访问到到其外层定义的变量)

缺点

回调函数是嵌套调用,外部回调函数执行结果是嵌套的回调执行的条件,不便于阅读,不便于异常处理

必须在启动异步任务之前指定所有任务

promise

使用原因

链式调用,避免了回调地域

可以在异步任务启动后绑定新的一致多个回调

属性

promiseState

  • promise状态
  • 值:
    • pending(初始状态,准备状态,未决定的)
    • resolve / fullfilled(成功,解决)
    • reject(失败,拒绝)
  • 状态改变一次后就不会再更改,改变后立即放入微队列内
  • 改变方法:
    • resolve(value) : pending -> resolved
    • reject(reson) : pending -> rejected
    • 抛出异常:pending -> rejected

promiseResult

  • promise结果
  • promiseResult 保存异步任务 成功/失败的结果
  • 只有resolve和reject可以改变这个值

基本流程

  1. 新建一个promise对象,状态为pedding
  2. 执行异步操作,
    1. 成功:
      1. 执行resolve()函数,promise对象状态为resolved
      2. 执行.then,执行onResolved()函数,返回新的promise对象
    2. 失败
      1. 执行reject()函数,promise对象状态为rejected
      2. 执行.then或者.catch,执行onRejected()函数,返回新的promise对象
  • 实际运行时,改变状态和指定回调函数(指定而不是执行)的代码先后顺序任意
    • 先改变状态后指定回调:
      • promise参数中在直接写了resolve()/reject(),即作为同步代码执行
      • 在then调用外嵌套了异步函数,即.then作为异步函数执行
    • 先指定回调后改变状态:
      • 在promise函数中的异步函数内写了resolve()/reject(),作为异步函数时等待(这种情况比较多、例如io、数据库、ajax操作)
      • .then为同步
  • 当状态改变且回调已经指定好后,才会得到数据

语法

复制代码
//promise参数为函数,该函数的参数默认有resolve、reject两个函数类型参数
new Promise((resolve,reject)=>{
	//...期间的同步代码会正常执行,异步代码会正常放入该去的队列

	//resolve('成功');改变后调用onResolved函数
	//reject('失败');改变后调用onRejected函数

	//更改完状态再将相应代码放入微队列,等待同步代码执行完后再执行
	//promise和then是对应出现的,then返回的也是promise对象
	//then可以一直套用下去,并且then中任务能确保异步的顺序
}).then(onResolved,onRejected);
.catch(onRejected)
.finally()
//也可以是.finally 即不论怎么样都执行 等同于.then(fn,fn)
- 通常用于常规清理
- finally 处理程序没有得到前一个处理程序的结果(它没有参数)。而这个结果被传递给了下一个合适的处理程序。
- 如果 finally 处理程序返回了一些内容,那么这些内容会被忽略。
- 当 finally 抛出 error 时,执行将转到最近的 error 的处理程序。

promise实例对象方法

then

then(onResolved,onRejected);

  • onResolved:成功时的回调

  • onRejected:可选,失败时的回调

  • 返回值:

    • 一个新的Promise实例,由then指定的回调函数的执行结果决定

      • 执行结果为结果抛出异常:返回状态为rejected,结果为抛出异常的promise
      • 执行结果为非promise:返回状态为resolved,结果为由then指定的回调函数的执行结果的promise
      • 执行结果为promise:返回该由then指定的回调函数的执行结果
      • 默认为返回状态为resolved,结果为undefined的Promise

      let p = new Promise((resolve, reject) => {
      setTimeout(() => {
      resolve('OK');
      }, 1000);
      });
      p.then(value => {
      return new Promise((resolve, reject) => {
      resolve(" success" );
      });
      }).then(value => {//此时为一个状态为resolved,结果为success的promise调用.then
      console.log(value); //success
      }).then(value => {//调用者没有return,所以为默认值,为一个状态为resolved,结果为undefined的默认Promise调用
      console.log(value);//undefined
      })

  • 作用:

    • 为promise实例添加状态改变时的回调函数
    • 当前面的Promise状态改变时,.then()根据其最终状态,选择特定的状态响应函数执行
  • 特性:

    • 因为返回一个新的Promise实例,所以它可以链式调用
    • 通过then的链式调用串联多个同步/异步任务
  • 如果返回新的promise,那么下一级.then()会在新的promise状态改变之后执行

  • 如果返回其他任何值,则会立即执行下一级.then()

  • 返回一个带状态的primose,可以作为下一个promise的参数

此时状态改变遵循参数的状态而不是调用的

复制代码
let p1 = new Promise((resolve, reject)=>{
	reject("拒绝");
});
new Promise((resolve, reject)=>{ 
	resolve(p1);//即遵循p1而不是resolve
}).then(
msg=>{console. log(msg);},
error=>{console. log(error+'error');
})
  • then的多次调用也会在转变为对应状态时依次执行

    let p = new Promise((resolve,reject)=>{
    resolve('成功')
    })
    //当p转变为resolve状态时,即会一次调用以下两个then
    //但是如果是reject,因为then中没有指定失败的回调函数,所以不执行(没有对应执行的)
    p.then(value =>{
    console.log(value)
    })
    p.then(value =>{
    console.log(value+"1")
    })

catch

catch(onRejected)

  • onRejected:失败时的回调
  • 捕获上文同步错误(reject throw),并且可以再次抛出(throw)由后续catch处理
  • 只关心失败情况 等同于.then(null,onRejected),类似语法糖

promise函数对象方法

resolve

Promise.resolve(value)

  • value为非promise对象时,返回值为成功promise对象
  • value为promise对象时,则返回值为该value参数的结果
  • 快速返回一个成功/失败的promise对象

reject

Promise.reject(reason)

  • 返回结果与reason无关,都为失败的promise对象
  • 返回的失败promise对象的结果为reason

all

该方法用于多个promise实例,包装成一个新的promise实例,即等待所有promise准备就绪
Promise.all([p1,p2,p3]);

  • 参数为一个包含n个promise的数组
  • 返回值:
    • 成功则返回一个成功primise对象,结果为包含了p1,p2,p3的返回值的数组,该数组顺序只和源代码有关,和等待时长无关

    • 失败则返回一个失败promise对象,结果为失败的promise参数的结果,且会忽略其他promise结果

      Promise.all([p1, p2]).then((result) => {
      console.log(result) //['成功了', 'success']
      }).catch((error) => {
      console.log(error) //
      })

race

Promise.race([p1,p2,p3]);

  • 参数为一个包含n个promise的数组
  • 返回值为一个promise,状态和结果为第一个完成的promise参数的状态和结果

promise.allSettled

对于每个promise都获取了其状态和value/reason

复制代码
let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/remy',
  'https://no-such-url'
];

Promise.allSettled(urls.map(url => fetch(url)))
  .then(results => { // (*)
    results.forEach((result, num) => {
      if (result.status == "fulfilled") {
        alert(`${urls[num]}: ${result.value.status}`);
      }
      if (result.status == "rejected") {
        alert(`${urls[num]}: ${result.reason}`);
      }
    });
  });

Promise.any

与 Promise.race 类似,区别在于 Promise.any 只等待第一个 成功的 promise,并将这个 fulfilled 的 promise 返回。如果给出的 promise 都 rejected,那么返回的 promise 会带有 AggregateError ------ 一个特殊的 error 对象,在其 errors 属性中存储着所有 promise error。

异常处理

异常穿透现象:只需要在最后一个then后调用catch就可以捕获所有异常

复制代码
let p = new Promise((resolve, reject) => {
	setTimeout(() => {
		reject('Err');
	}, 1000);
});

p.then(value => {
	console.log(111);
}).then(value => {
	console.log(222);
}).then(value => {
	console.log(333);
}).catch(reason => {
	console.warn(reason);
})

中断promise链

  • 即中断已经书写好的promise.then链式调用,只执行前面的不执行后面的

  • 方法:在回调函数中返回一个pendding状态的promise对象

    let p = new Promise((resolve, reject) => {
    setTimeout(() => {
    reject('Err');
    }, 1000);
    });

    p.then(value => {
    console.log(111);
    return new Promise(()=>{})
    }).then(value => {
    console.log(222);
    }).then(value => {
    console.log(333);
    }).catch(reason => {
    console.warn(reason);
    })

封装核心功能

这种返回值为一个 Promise 对象的函数称作 Promise 函数,它常常用于开发基于异步操作的库。

复制代码
function print(delay, message) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(message);
            resolve();
        }, delay);
    });
}
print(1000, "First").then(function () {
    return print(4000, "Second");
}).then(function () {
    print(3000, "Third");
});
  • then、catch 和 finally 顺序可以颠倒,但是最好按 then-catch-finally 的顺序编写程序。
  • then、finally都可以多次使用,会按顺序执行,但是catch只会执行第一个,除非 catch 块里有异常。所以最好只安排一个 catch块。
  • then 块默认会向下顺序执行,return 是不能中断的,可以通过 throw 来跳转至 catch 实现中断。
  • 当需要多次顺序执行异步操作的时候,例如,如果想通过异步方法先后检测用户名和密码,需要先异步检测用户名,然后再异步检测密码的情况下, Promise显然优于回调函数。

嵌套返回

缺点:无法取消 Promise,错误需要通过回调函数捕获。

复制代码
.then(
  value > {
    return new Promise((resolve,reject) = {//其后的then是对该返回值promise的处理
    	reject( "处理失败" );
    })
.then(
null, 
r =console.log(r);
});
//处理失败

回调函数转promise

  1. 将函数主体套入new promise ((resolve,reject)=>{...})
  2. 将promise置为一个新函数的返回值 方便后续使用
  3. 将原函数主体的成功、失败函数改为resolve(),reject(),到此封装完成
  4. 调用新函数,添加.then并在其中写上resolve(),reject()的具体内容,处理结果

util.promisify

通过此方法可以将回调函数风格简便的转换成promise风格

复制代码
//引入util 模块
const util = require('util');
//引入fs模块
const fs = require('fs');
//返回一个新的函数
let mineReadFile = util. promisify(fs.readFile);
mineReadFile('./resource/content.txt'). then(value=>{ ... });|

async+await

  • async/await 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里

  • await只在async函数内工作,让js引擎等待直到promise完成并返回结果

  • await 实际上会暂停函数的执行,直到 promise 状态变为 settled,然后以 promise 的结果继续执行。这个行为不会耗费任何 CPU 资源,因为 JavaScript 引擎可以同时处理其他任务:执行其他脚本,处理事件等。

  • 相比于 promise.then,它只是获取 promise 的结果的一个更优雅的语法。并且也更易于读写。

    async function main(){
    try{
    //读取第一个文件的内容
    let datal = await mineReadFile(' ./ resource/1. html' );
    let data2 = await mineReadFile(' . / resource/2. html');
    let data3 = await mineReadFile(' ./resource/3.html' );
    console.1og(data1 + data2 + data3);
    }catch(e){ .. }

返回值

  • async确保函数返回promise,会将非promise的返回值包装
  • 非promise数据:返回一个成功的,结果为async函数返回值的promise对象
  • promise对象:返回该由async指定的回调函数的执行结果
  • 抛出异常:返回状态为rejected,结果为该抛出异常的promise

await

  • await右侧表达式一般为promise对象,也可以为其他值
  • 如果右侧为promise对象,返回的是promise成功的值
  • 如果是其他值,将该值作为await返回值
  • 可以和promise.all一起使用let results = await Promise.all([...]);

特点

  • await必须在async中,async中可以没有await
  • 如果await右侧promise失败了,就会抛出异常,需要try{}catch{}捕获
    • 即在try{}catch{}中写await比较保险

      async function f() {
      await Promise.reject(new Error("Whoops!"));
      }

      async function f() {
      throw new Error("Whoops!");
      }

      async function f() {
      try {
      let response = await fetch('http://no-such-url');
      } catch(err) {
      alert(err); // TypeError: failed to fetch
      }
      }
      f();

async+await和Promise对比

  • 相同:
    • 都是异步编程的一种解决方案
    • 都是非阻塞
  • 区别:
    • 概念区别
      • Promise的出现解决了传统callback函数导致的"地域回调"问题
      • async await 也是异步编程的一种解决方案,他遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。
    • 语法区别
      • promise是链式调用
      • async await代码使得异步代码看起来像同步代码,await的本质是可以提供等同于"同步效果"的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
  • async :
    • 优点:
      • 处理 then 的调用链,能够更清晰准确的写出代码
      • 也能优雅地解决回调地狱问题。
    • 缺点:
      • 因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。
      • async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。

async+await和Generator对比

  • 内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行
  • 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
  • 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
相关推荐
悟空瞎说3 分钟前
深度解析:Vue3 为何弃用 defineProperty,Proxy 到底强在哪里?
前端·javascript
leafyyuki5 分钟前
告别 Vuex 的繁琐!Pinia 如何以更优雅的方式重塑 Vue 状态管理
前端·javascript·vue.js
沐知全栈开发9 分钟前
《jEasyUI 格式化列》
开发语言
0xDevNull18 分钟前
JDK 25 新特性概览与实战教程
java·开发语言·后端
某人辛木20 分钟前
nodejs下载安装
开发语言·前端·javascript
笨笨饿27 分钟前
34_数据结构_栈
c语言·开发语言·数据结构·人工智能·嵌入式硬件·算法
爱码驱动32 分钟前
文件操作和IO
java·开发语言·io·文件操作
坊钰34 分钟前
Java 反射机制
java·开发语言
单片机学习之路38 分钟前
【Python】输入print函数
开发语言·前端·python
李昊哲小课44 分钟前
Python办公自动化教程 - 第1章 openpyxl基础入门 - 第一次用代码操控Excel
开发语言·python·excel·openpyxl