首页 > 其他分享 >异步总结

异步总结

时间:2023-05-28 23:55:05浏览次数:44  
标签:总结 异步 .. await 生成器 Promise promise

@

目录

Promise

对于Promise的理解

解决的难点(相对于回调方式的异步)

  1. 解决回调地狱问题

    大脑对于事情的计划方式是线性的、阻塞的、单线程的语义,但是回调表达异步流程的方式是非线性的、非顺序的,这使得正确推导这样的代码难度很大,将在多个回调中反复横条,且错误追踪不易。

    promise以队列的方式组合了异步流程,以promise链代替了回调金字塔。

    生成器以看似同步顺序的方式表达异步流程。

  2. 解决信任问题

回调会受到控制反转的影响,因为回调暗中把控制权交给第三方(通常是不受你控制的第三方工具!)来调用你的代码。这种控制转移导致一系列麻烦的信任问题,比如回调被调用的次数是否会超出预期。

不同的方向理解(使用)Promise

  1. 封装未来值

    由于 Promise 封装了依赖于时间的状态——等待底层值的完成或拒绝,所以Promise 本身是与时间无关的。因此, Promise 可以按照可预测的方式组成(组合),而不用关心时序或底层的结果。

    Promise 是一种封装和组合未来值的易于复用的机制。

  2. 事件完成(事件触发)

    Promise类似订阅发布模式中的订阅管理器,异步完成类似发布(发布事件),Promise通过then和catch收集订阅,或者传递完成的事件,then和catch函数类似事件订阅。

    Promise 的决议:一种在异步任务中作为两个或更多步骤的流程控制机制,时序上的 this-then-that。
    Promise 决议不仅可以将 Promise 作为未来值查看,一样会涉及发送消息。它也可以只作为一种流程控制信号。

Promise构造

在 Promise 的创建过程new构造器或resolve和reject方法)中或在查看其决议结果then和catch方法)过程中的任何时间点上出现了一个 JavaScript 异常错误,比如一个 TypeError 或 ReferenceError,那这个异常就会被捕捉, 并且会使这个 Promise 被拒绝

new Promise(fn) 构造器

必须提供一个函数回调。这个回调是同步的或立即调用的。 这个函数接受两个函数回调,用以支持 promise 的决议。通
常我们把这两个函数称为 resolve(..) 和 reject(..):

var p = new Promise( function(resolve,reject){
    // resolve(..)用于决议/完成这个promise
    // reject(..)用于拒绝这个promise
} );

reject(..) 就是拒绝这个 promise;rejecti不会展开Promise/thenable,如果向reject(..) 传入一个 Promise/thenable 值, 它会把这个值原封不动地设置为拒绝理由。后续的拒绝处理函数接收到的是你实际传给 reject(..) 的那个Promise/thenable,而不是其底层的立即值

resolve(..) 既可能完成 promise,也可能拒绝,要根据传入参数而定。

  • 传给 resolve(..) 的是一个非 Promise非 thenable 的立即值,这个 promise 就会用这个值完成。

  • 传给 resolve(..) 的是一个真正的 Promise 或 thenable 值,这个值就会被递归展开,并且(要构造的) promise 将取用其( Promise 或 thenable )最终决议值或状态。

如果使用多个参数调用 resovle(..) 或者 reject(..),第一个参数之后的所有参数都会被默默忽略。

Promise.resolve(..) 和 Promise.reject(..)

创建一个已被拒绝的 Promise 的快捷方式是使用 Promise.reject(..) ,rejecti不会展开Promise/thenable,如果向reject(..) 传入一个 Promise/thenable 值, 它会把这个值原封不动地设置为拒绝理由。后续的拒绝处理函数接收到的是你实际传给 reject(..) 的那个Promise/thenable,而不是其底层的立即值

var p1 = new Promise( function(resolve,reject){
	reject( "Oops" );
} );
var p2 = Promise.reject( "Oops" );
// p1和p2是等价的

Promise.resolve(..) 常用于创建一个已决议(完成或拒绝)的 Promise;

  • Promise.resolve(..) 会展开 thenable 值。在这种情况下,返回的 Promise 采用传入的这个 thenable 的最终决议值,可能是完成,也可能是拒绝
var fulfilledTh = {
	then: function(cb) { cb( 42 ); }
};
var rejectedTh = {
	then: function(cb,errCb) {
    	errCb( "Oops" );
    }
};
var p1 = Promise.resolve( fulfilledTh );
var p2 = Promise.resolve( rejectedTh );
// p1是完成的promise
// p2是拒绝的promise
  • 如果传入的是真正的 Promise, Promise.resolve(..) 什么都不会做,只会直接把这个值返回。

then(..) 和 catch(..)

每个 Promise 实例(不是 Promise API 命名空间)都有 then(..) 和 catch(..) 方法,通过这两个方法可以为这个 Promise 注册完成拒绝处理函数。 Promise 决议之后,立即会调用这两个处理函数之一,但不会两个都调用,而且总是异步调用

then(..)

then(..) 接受一个或两个参数第一个用于完成回调第二个用于拒绝回调。如果两者中的任何一个被省略或者作为非函数值传入的话,就会替换为相应的默认回调默认完成回调只是把消息传递下去而默认拒绝回调则只是重新抛出(传播)其接收到的出错原因

catch(..)

只接受一个拒绝回调作为参数,并自动替换默认完成回调。换句话说,它等价于 then(null,..)

p.then( fulfilled );
p.then( fulfilled, rejected );
p.catch( rejected ); // 或者p.then( null, rejected )

then(..) 和 catch(..) 也会创建并返回一个新的 promise,这个 promise 可以用于实现Promise 链式流程控制。

如果完成或拒绝回调抛出异常返回的 promise 是被拒绝的。(即then和catch中有异常返回的promise的状态是拒绝的)

如果任意一个回调返回非 Promise、非 thenable 的立即值这个值会被用作返回 promise 的完成值。 (即then和catch正常运行返回的promise的状态是正常的

如果完成处理函数返回一个 promise 或 thenable,那么这个值会被展开,并作为返回promise 的决议值

判别Promise类型:具有 then 方法的类型(thenable 鸭子类型)

判别某个值是否是promise,常用方法可以通过 p instanceof Promise 来检查。但是并不准确,存在一下主要原因:

  • Promise 值可能是从其他浏览器窗口(iframe 等)接收到的。这个浏览器窗口自己的 Promise 可能和当前窗口 /frame 的不同,因此这样的检查无法识别 Promise实例。
  • 库或框架可能会选择实现自己的 Promise,而不是使用原生 ES6 Promise 实现。实际上,很有可能你是在早期根本没有 Promise 实现的浏览器中使用由库提供的 Promise。

识别 Promise(或者行为类似于 Promise ) :任何具有 then(..) 方法的对象和函数。 任何这样的值就是Promise 一致的 thenable。

根据一个值的形态(具有哪些属性)这个值的类型做出一些假定。这种类型检查(typecheck)一般用术语鸭子类型(duck typing)来表示——“如果它看起来像只鸭子,叫起来像只鸭子,那它一定就是只鸭子”

注意:如果 thenable 鸭子类型误把不是 Promise 的东西识别为了 Promise,可能就是有害的。 (在 ES6 之前,社区已经有一些著名的非 Promise 库恰好有名为 then(..) 的方法。这些库中有一部分选择了重命名自己的方法以避免冲突。而其他的那些库只是因为无法通过改变摆脱这种冲突,就很不幸地被降级进入了“与基于 Promise 的编码不兼容”的状态。 )

标准决定劫持之前未保留的——听起来是完全通用的——属性名 then。这意味着所有值(或其委托),不管是过去的、现存的还是未来的,都不能拥有 then(..) 函数,不管是有意的还是无意的;否则这个值在 Promise 系统中就会被误认为是一个 thenable,这可能会导致非常难以追踪的 bug。

Promise局限性

  1. 顺序错误处理

    在promise链中,拒绝错误处理函数(catch函数)能处理该函数之前的promise未处理的错误;但是因为没有为promise链中产生的promise保留引用,无法做到可靠的检查错误。

  2. 单一值:

    Promise 只能有一个完成值或一个拒绝理由,简单的例子中,这不是什么问题,但是在更复杂的场景中,可能这是一种局限;一般的建议是构造一个值封装(比如一个对象或数组)来保持这样的多个信息。这个解决方案可以起作用,但要在 Promise 链中的每一步都进行封装和解封,就十分丑陋和笨重了。

  3. 单决议:

    Promise 只能被决议一次(完成或拒绝)。在许多异步情况中,你只会获取一个值一次,所以这可以工作良好。但是,对于很多其他异步模式:类似于事件和 / 或数据流的模式支持不够。

  4. 惯性:

    基于回调异步的代码,重构为promise(promise化),有心智负担,原生 ES6 Promise 并没有提供辅助函数用于这样的 promisory (“Promise” +“factory” Promise工厂)封装,但多数库都提供了这样的支持,或者你也可以构建自己的辅助函数。

  5. 无法取消的 Promise:

    一旦创建了一个 Promise 并为其注册了完成和 / 或拒绝处理函数,如果出现某种情况使得这个任务悬而未决的话,你也没有办法从外部停止它的进程;单独的 Promise 不应该可取消,但是取消一个序列(promise链)是合理的,因为你不会像对待 Promise
    那样把序列作为一个单独的不变值来传送。

生成器

概述

生成器就是一类特殊的函数,相较于传统函数是同步运行,不能暂停与恢复(除非有异常),可以一次或多次启动和停止,并不一定非得要完成。 生成器停止(由迭达器停止)后,交出线程控制权(程序控制权),由迭达器来恢复 ,继续运行。

使用方式

运行生成器创建了一个迭代器对象,把它赋给了一个变量(如it),用于控制生成器。然后调用 it.next(),指示生成器开始继续运行,停在下一个 yield 处或者直到生成器结束。

next() 调用返回一个对象。这个对象有两个属性: done 是一个 boolean 值,标识迭代器的完成状态,当为true时,表示生成器结束; value 中放置迭代值。

关于生成器结束:

  1. 迭达器调用next():返回对象中, done属性为true时表示结束,迭达器不再迭达。
  2. 迭达器调用return():返回结果(将值传递给迭达器的value属性)并结束生成器(迭达器的done属性为true);如果 生成器内部有try...finally代码块,且正在执行try代码块,那么return()方法会导致立刻进入finally代码块,执行完以后,整个生成器才会结束。
  3. 迭达器调用throw():跌达器抛出异常,然后传入生成器,如生成器未被捕获,该生成器将结束,否则继续运行生成器;throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法;throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法(可以把throw方法理解为,需要处理)。

特性

  1. 控制权转移:生成器可以由迭达器暂停和开始,暂停交出线程控制权,等待迭达器恢复或者不恢复
  2. 双向消息传递 :通过迭达器可以生成器传递数据,也可以向迭达器传递数据,内建消息输入输出功能;
  3. 生成值:最基础特性,也是名字的由来,可以无限生成,只要done不为true;

迭达器与可迭达(迭达对象)

迭达器:对象中有next方法;
可迭达对象(iterable):指包含一个可以在其值上迭达的迭达器的对象,(对象包含迭达器,迭达器可以迭达对象上的值);

  1. 迭代器是一个定义良好的接口,用于从一个生产者一步步得到一系列值。 JavaScript 迭代器的接口,与多
    数语言类似,就是每次想要从生产者得到下一个值的时候调用 next()。
  2. 从 ES6 开始,从一个 iterable 中提取迭代器的方法是: iterable 必须支持一个函数,其名称是专门的 ES6 符号值 Symbol.iterator。调用这个函数时,它会返回一个迭代器。通常每次调用会返回一个全新的迭代器,虽然这一点并不是必须的。

生成器,迭达器,可迭达

每次构建一个迭代器,实际上就隐式构建了生成器的一个实例,通过这个迭代器来控制的是这个生成器实例。不同的生成器实例相互独立。

当你执行一个生成器,就得到了一个迭代器 ,严格说来,生成器本身并不是可迭达;

ES6中for..of 循环,可以迭达迭达对象(可迭达),for..of可以迭达运行生成器构建的迭达器,是因为该迭达器是一个可迭达对象。

生成器与异步

解决异步同步方式管理(顺序管理)

因为生成器具有交出控制权的特性,不阻塞线程的特性,所以可以通过将生成器的迭达器交予异步来启动生成器(如果有需要可以通过消息传递功能,向生成器传递异步结果),来达到以看似同步的方式管理异步流程。

看似阻塞同步的代码,实际上并不会阻塞整个程序,它只是暂停或阻塞了生成器本身的代码。

从本质上而言,我们把异步(不管是回调异步或者Promise异步)作为实现细节抽象了出去,使得生成器可以以同步顺序的形式追踪流程控制:

解决异步错误跟踪(同步的方式处理错误)

生成器 yield 暂停的特性意味着我们不仅能够从异步函数调用得到看似同步的返回值,还可以同步捕获来自这些异步函数调用的错误。

生成器构建的迭达器也可以手工回传错误(通过throw函数),

注意:如果生成器内部抛出的错误,外部需要自行捕获,不然会造成程序运行错误从而结束。

生成器实现看似同步的错误处理(通过 try..catch),是异步代码的可读性和合理性的巨大进步。

生成器 +Promise

ES6中异步的完美组合是生成器+Promise

  1. 生成器:看似同步的异步代码控制。
  2. Promise :可信任性和可组合性 。

获得 Promise 和生成器最大效用的最自然的方法就是 yield 出来一个 Promise,然后通过这个 Promise 来控制生成器的迭代器。

注:更多生成器语法:https://es6.ruanyifeng.com/#docs/generator

async

它就是 Generator(生成器) 函数的语法糖。
生成器运行返回迭达器,迭达器,需要手动运行,有第三方库可以自动执行迭达器(根据生成器内部不同的异步方式:基于回调函数和Promise),有了async和await函数后,原生支持自动执行。

使用

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。

返回值为Promise

async函数内部return语句返回的值,会成为then方法回调函数的参数.
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变(函数运行完成),除非遇到return语句或者抛出错误

await 命令

正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

另一种情况是,await命令后面是一个thenable对象(即定义了then方法的对象),那么await会将其等同于 Promise 对象。

await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行(类似于生成器中未捕获错误,错误往外抛).

注意:如果需要即使前一个异步操作失败,也不要中断后面的异步操作(捕获错误,恢复运行)。可以通过一下方法:

  1. 将可能错误的Promise放入放在try...catch结构里面,这样不管这个Promise操作是否成功,后续的await都会执行。
async function f() {
  try {
    await Promise.reject('出错了');
  } catch (e) {
    console.log('处理错误',e);
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world
  1. 另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。
async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

错误处理

如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject。

如需要捕获异常,继续运行可查看 await命令 处理异常的方式;

注意:try...catch只能捕获单次异步异常,不能捕获多次异常(和生成器一样);

async function f() {
  try {
    await Promise.reject('出错了1')    //  捕获单次异常
    await Promise.reject('出错了2')    //  这里的代码将不再运行,不会再捕获该异常
  } catch (e) {
    console.log('处理错误', e)
  }
  return await Promise.resolve('hello world')   //  恢复异常后继续运行
}

f()
  .then((v) => console.log(v))
  .catch((err) => {
    console.log(err)
  })
// 处理错误 出错了1
// hello world

顶层 await

早期的语法规定是,await命令只能出现在 async 函数内部,否则都会报错。
从 ES2022 开始,允许在模块的顶层独立使用await命令。它的主要目的是使用await解决模块异步加载的问题。它保证只有异步操作完成,模块才会输出值,才能使用模块。

顶层await的一些使用场景:

// import() 方法加载
const strings = await import(`/i18n/${navigator.language}`);

// 数据库操作
const connection = await dbConnector();

// 依赖回滚
let jQuery;
try {
  jQuery = await import('https://cdn-a.com/jQuery');
} catch {
  jQuery = await import('https://cdn-b.com/jQuery');
}

顶层的await命令有点像,交出代码的执行权给其他的模块加载,等异步操作完成后,再拿回执行权,继续向下执行。

标签:总结,异步,..,await,生成器,Promise,promise
From: https://www.cnblogs.com/niehao/p/17439212.html

相关文章

  • 总结20230528
    代码时间(包括上课)2h代码量(行):50行博客数量(篇):1篇相关事项:1、今天直接凌晨五点才到宿舍,连夜整的无人机,为后天的比赛准备。2、今天上午上的计算机网络,由于比赛冲突,请假了没去上。3、今天下午的web上机也没去,正好赶上web报告也写完。4、晚上也是连夜通宵整的无人机。......
  • 软件工程课程总结
    光阴似箭,日月如梭,不知不觉已经到了期末,在此简单做一下对软件工程这门课的课程总结。在学习软件工程课程的这一学期中,我通过王建民老师的讲解和实践操作,学习到了很多软件开发的基本理论知识和开发方法。感谢王建民老师对我们的耐心指导和教授。在软件开发中,团队合作是非常重要的。......
  • 团队项目事后总结
    今天是5.28日,我们团队第二阶段的开发也告一段落。总的来说,开发过程虽然有坎坷,但是最后的结果还是比较好的。我们将一开始的安全人脸识别目标变成了学生课堂专注度测试系统。这对于我们来说是完全陌生的领域,不管是技术上的障碍,还是团队合作中的障碍,都是很难解决的问题。我......
  • 第二次冲刺——团队总结
    今天我们团队进行了事后诸葛亮会议,我们准备来讨论一下关于本次项目的一些不足根据——“如果你可以重新来过,什么方面可以做得更好?”这个核心问题来进行讨论一、设想和目标首先,我们团队的项目是智能排班项目,我们的软件要解决门店排班费时费力的问题,为连锁店排班提供一个操作方便......
  • 软件工程期末总结
    软件工程期末总结作为一门涉及到实际应用的学科,软件工程在我的学习中给我带来了巨大的启示和认识。这门课程的学习,不仅丰富了我的知识面,还让我深刻地体会到了自主学习的重要性。在这篇期末总结中,我将分享我学习软件工程时的体会和思考。一、认识软件工程软件工程是研究如何高效......
  • 项目总结会议
    经过讨论项目的存在问题总结出以下三个主要问题。主要是地图的定位设备开机位置无法实现。而后是售卖机的页面展示不够美观。再者是系统的操作是否符合用户的需求,是否过于繁琐。 ......
  • 个人软工结课总结
    大家好,光阴似箭日月如梭,时光过的总是很快,一转眼这学期已经过去了,我还是那个弱小无助的自己,这学期觉得比上学期要坚强一些,没想到回过头来更加脆弱了。不过收获还是有的。这学期学习了安卓的许多控件,四大组件还有接口的回调设计思路。除了安卓,我还对web端的框架进行了一定的学习,现......
  • Oracle 死锁与慢查询总结
    查看死锁SELECTs.sid"会话ID",s.lockwait"等待锁",s.event"等待的资源/事件",--最近等待或正在等待的资源/事件DECODE(lo.locked_mode,0,'尚未获得锁',1,NULL,2,'行共享锁',3,'行排它锁',4,'共享表锁',5,'共享行排它锁',6,......
  • 软件工程课程本学期总结
    大二下学期过的很快,一转眼也快结束了;不能不让人感叹一句光阴似箭,时间从来不等人,这是我本学期的课程总结。在这个学期中,我学习了大量的软工相关的知识,从专业课程到项目实践,从个人开发到团队合作,这种全方位的学习方式让我感到收获很多。我在这个学期中遇到的一些问题,以及通......
  • 前端自动识别CAD图纸提取信息方法总结
    前言CAD图纸自动识别和提取信息具有许多意义,包括以下几个方面:提高工作效率:传统上,对于大量的CAD图纸,人工识别和提取信息是一项耗时且繁琐的任务。通过自动化这一过程,可以大大提高工作效率,节省时间和人力资源。减少错误和精度提升:人工处理CAD图纸容易出现错误,例如错读数字或......