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 表示紧跟在后面的表达式需要等待结果。
相关推荐
小技与小术2 分钟前
数据结构之树与二叉树
开发语言·数据结构·python
旭日猎鹰20 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
hccee24 分钟前
C# IO文件操作
开发语言·c#
hummhumm29 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
J老熊39 分钟前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
zmd-zk1 小时前
flink学习(2)——wordcount案例
大数据·开发语言·学习·flink
好奇的菜鸟1 小时前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
一条晒干的咸魚1 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
Alive~o.01 小时前
Go语言进阶&依赖管理
开发语言·后端·golang
花海少爷1 小时前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript