你不知道的JS(十四):Promise

  • 前面,我们确定了通过回调表达程序异步和管理并发的两个主要缺陷:缺乏顺序性可信任性
  • 首先要解决的是控制反转的问题,如果能够把控制反转再反转回来,那将会怎么样?这种范式称为Promise。
  • 目前绝大多数 JavaScript/DOM 平台新增的异步API都是基于Promise构建的,所以深入学习Promise很有意义。

一、什么是Promise

1. 未来值

现在值与将来值

  • 语句有可能是现在完成,也可能是将来完成。
  • 为了统一处理现在和将来,我们把它们都变成将来,即所有操作都是异步。

2. Promise值

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

决议

  • Promise的决议结果可能是拒绝而不是完成。拒绝值和完成的Promise不一样:完成值总是编程给出的,而拒绝值,通常称为拒绝原因。

  • 通过Promise,调用then()实际上可以接受两个函数,第一个用于完成情况,第二个用于拒绝情况。

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

不变值

  • 一旦Promise决议,它就永远保持这个状态。此时它就成为了不变值,可以根据需要多次查看。

  • Promise决议后就是外部不可变的值,我们可以安全地把这个值传递给第三方,并确信它不会被有意无意地修改。

3. 完成事件

  • 也可以从另一个角度看待Promise的决议:一种在异步任务中作为两个或更多步骤的流程控制机制,时序上的this-then-that。

  • 我们只需要知道foo()函数什么时候结束,这样就可以进行下一个任务。(假设foo()为耗时操作)

  • 使用回调的话,通知的是foo()调用回调。而使用Promise的话,我们把这个关系反转了过来,侦听来自foo()的完成事件,然后在得到通知的时候,根据情况继续。

二、具有then方法的鸭子类型

  • 根据一个值的型态(具有哪些属性)对这个值的类型做出一些假定,这种类型检查一般用术语鸭子类型来表示。
  • 在Promise领域,一种重要的细节是如何确定某个值是不是真正的Promise。或者说,是不是行为类似于Promise。
  • p instanceof Promise 不足以作为检查方法。
  • 识别Promise就是定义某种成为thenable的东西,将其定义为任何具有then()方法的对象和函数

三、Promise的信任问题

回顾一下回调编码的信任问题:

  • 调用回调过早
  • 调用回调过晚(或没有调用)
  • 调用次数过多或过少
  • 没有成功地将参数传入到回调中
  • 吞掉可能出现的报错和异常

Promise的特性就是专门用来为这些问题提供一个有效的可复用的答案。

1. 调用过早

  • 原因:一个任务有时同步完成,有时异步完成。
  • Promise就不必担心这种问题,即使是立即完成的Promise,then方法中的回调还是异步调用的。

2. 调用过晚

  • 一个Promise决议后,这个Promise上所有的通过then()注册的回调都会在下一个异步时机点上依次被立即调用。这些回调中的任意一个都无法影响或延误对其他回调的调用。
p.then(function() {
	p.then(function() {
		console.log("C")
	})
	console.log("A")
})
p.then(function() {
	console.log("B")
})
// A B C

这里C无法打断或抢占B。

3. 回调未调用

  • 没有任何东西(甚至JavaScript)能阻止Promise向你通知它的决议。如果你对一个Promise注册了一个完成回调和一个拒绝回调,那么Promise在决议时总会调用其中一个。

  • 但是回调本身包含错误,就看不到期望的结果,当回调还是会调用。

  • 如果Promise本身永远不会被决议呢?

  • 使用一种称为竞态的高级抽象机制。

Promise.race([
	foo(), // 可能不会有结果
	timeoutPromise(3000); // 3秒后调用reject
])

4. 调用次数过多或过少

  • 由于Promise只能被决议依次,所以任何通过then()注册的回调都只会调用一次。

5. 是可信任的Promise吗

  • Promise并没有摆脱回调,只是改变了传递回调的位置,并不是把回调传递给foo(),而是从foo()上得到某个东西(Promise),然后把回调传递给它。

(原文还有其他问题讨论)

四、链式流

  • 每次对Promise调用then(),它都会创建并返回新的Promise,我们可以将其链接起来。
  • 我们构建的Promise链不仅是一个表达多步异步序列的流程控制,还是一个从步骤到下一个步骤传递消息的消息通道。

如果某个步骤出错怎么办?

  • 调用then()时的完成处理函数或拒绝处理函数如果抛出异常,都会导致下一个Promise因为这个异常而立即被拒绝。
  • 如果你调用Promise的then()只传入一个完成处理函数,一个默认拒绝处理函数就会顶替上来。
  • 因此,错误可以沿着Promise链传播下去,直到遇到显式定义的拒绝出来函数。
  • 同样,也有默认完成处理函数。

五、错误处理

  • 同步的try...catch结构无法用于异步代码模式。

  • Promise采用分离回调风格,一个回调用于完成情况,一个回调用于拒绝情况。

1. 绝望的陷阱

  • Promise错误处理是一个“绝望陷阱”,Promise状态会吞掉所有错误,如果你忘记查看状态,这个错误就会默默地消失。
  • 所以,一些开发者建议在Promise链末尾使用Promise.catch()来捕获错误,但是catch中的错误就无法捕获了。

2. 处理未捕获的情况

  • 这不是一个容易解决的问题。
  • 有些Promise库增加了一些方法,用于类似于“全局未处理拒绝”处理函数的东西,这样就不会抛出全局错误,而是调用这个函数。
  • 在多数情况下使用良好,因为在Promise拒绝和检查拒绝之间没有很长的延迟。

六、Promise API概述

1. new Promise()构造器

构造器 Promise() 必须和new一起使用

var p = new Promise(function(resolve, reject) { 
    // 这部分立即执行
	// ...
	// resolve() 用于完成这个Promise
	// reject()用于拒绝这个Promise
})

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

  • Promise.resolve()用于创建一个已完成的Promise。如果传入的值是Promise,则什么都不做,直接把这个值返回。

  • Promise.reject()用于创建一个拒绝的Promise。与上面在构造器内调用reject()等价。

3. then()和catch()

  • then()接受一个或两个参数:第一个用于完成回调,第二个用于拒绝回调。如果两者中的任何一个被省略或者作为非函数值传入的话,就会替换为相应的默认回调。

  • 默认完成回调只是把消息传递下去,而默认拒绝则只是重新抛出其接收到的出错原因。

  • catch()只接受拒绝回调作为参数,相当于then(null, ...)

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

4. Promise.all()

  • 在所有成员都完成才会完成,如果有一个拒绝,就会立刻拒绝。
  • 这个数组可以是Promise,也可以是立即值,会通过Promise.solve()过滤,立即值会被规范为Promise。

5. Promise.race()

  • 只需要第一个完成的,其他的抛弃。
  • 那些被忽略的Promise不会取消,结果会被默默忽略。

超时竞赛:设置Promise超时(前面有例子说明)

七、Promise局限性

1. 顺序错误处理

  • 如果一个Promise链中没有错误处理,那么错误很容易被无意中默默忽略掉。
  • 还有注意变量的指向
var p = foo(42) // 这里的foo()代表构造Promie
.then(STEP2)
.then(STEP3)

这里的P指向的不是第一个Promise,而是STEP3

p.catch() // 可以捕获到上面任意步骤的错误

2. 单一值

  • Promise只能有一个完成值或一个拒绝理由。在简单例子中没有问题,但是在更复杂的场景中,你可能会发现这是一种局限。

3. 单决议

  • Promise只能决议一次,完成或拒绝。在许多异步情况中,你只会获取一个值一次,所以工作良好。
  • Promise无法支持多值决议处理。

4. 无法取消Promies

  • 一旦创建了一个Promise并为其注册了完成和拒绝处理函数,如果出现某种情况使得这个任务悬而未决的话,你也没有办法从外部停止它的进程。
  • 单独的Promise不应该可取消,但是取消一个序列是合理的。因为Promise值是一个不变的值(状态),而Promise序列不会有这种考虑。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在你提供的引用中,出现了一个错误信息,即"ReferenceError: Promise is not defined"。这个错误通常是由于缺少Promise对象的定义导致的。PromiseJavaScript中的一个内置对象,用于处理异步操作。 通常情况下,Promise对象是在现代浏览器和较新版本的Node.js中原生支持的。但是,如果你的环境是较旧版本的Node.js,可能会遇到该错误。为了解决这个问题,你可以尝试以下几个方法: 1. 确保你的Node.js版本符合要求:首先检查你使用的Node.js的版本,确保它支持Promise对象。如果你使用的是较旧的版本,考虑升级到较新的版本。 2. 添加Promise的Polyfill:如果你无法升级Node.js版本,你可以尝试添加Promise的Polyfill。Polyfill是一个JavaScript代码片段,可以模拟不被当前环境支持的功能。你可以在你的项目中引入一个Promise的Polyfill,例如es6-promise模块,并确保在使用Promise之前先引入该模块。 3. 使用Babel等工具进行转译:另一个解决方案是使用Babel等工具将你的代码转译成较旧版本的JavaScript,以便在旧版本的Node.js中运行。Babel可以将现代JavaScript语法转换为向后兼容的代码。 综上所述,"ReferenceError: Promise is not defined"错误通常是由缺少Promise对象的定义引起的。你可以尝试升级Node.js版本、添加Promise的Polyfill或使用Babel等工具进行转译来解决这个问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [bug ReferenceError: Promise is not defined](https://blog.csdn.net/avtcpm1927/article/details/101622630)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值