趁着过年,讲讲 Promise

人生代码

共 2772字,需浏览 6分钟

 ·

2021-02-11 10:13

趁着过年,将讲 Promise

想象一下,你是一位顶级歌手,粉丝们日日夜夜都在为你即将发行的歌曲而发愁。

为了缓解压力,你答应出版后寄给他们。你给你的粉丝一个列表。他们可以填写自己的电子邮件地址,这样当歌曲可用时,所有订阅方都能立即收到。即使出了什么大问题,比如工作室着火了,你不能发布这首歌,他们还是会得到通知。

每个人都快乐:你,因为人们不再挤你了,还有粉丝,因为他们不会错过这首歌。

这是我们在编程中经常遇到的现实类比:

一个“生成代码”,做一些事情,并需要时间。例如,通过网络加载数据的一些代码。这是一个“歌手”。

一旦“生产代码”准备好了,“消费代码”就会想得到它的结果。许多函数可能需要这个结果。这些就是“粉丝”。

promise是一个特殊的JavaScript对象,它将“生产代码”和“消费代码”链接在一起。根据我们的类比:这是“订阅列表”。“生成代码”需要花费任何时间来生成承诺的结果,而“承诺”在结果准备好时使所有订阅的代码都可以使用该结果。

这种类比并不十分准确,因为JavaScript承诺比简单的订阅列表更复杂:它们有额外的特性和限制。但从一开始就很好。

promise对象的构造函数语法是:

let promise = new Promise(function(resolve, reject{
  // executor (the producing code, "singer")
});

传递给new Promise的函数称为executor。创建新承诺时,执行程序自动运行。它包含最终产生结果的生成代码。用上面的比喻:执行人就是“歌手”。

它的参数resolve和reject是JavaScript本身提供的回调函数。我们的代码只在执行器内部。

当executor获得结果时,不管是快还是晚,都没有关系,它应该调用以下其中一个回调函数:

  • resolve(value)—如果作业成功完成,则使用结果值。

  • reject(error)——如果发生了错误,error就是error对象。

总而言之:执行程序自动运行并尝试执行一项工作。当它完成尝试时,如果成功就调用resolve,如果有错误就调用reject。

新的promise构造函数返回的promise对象有以下内部属性:

状态——最初是“pending”,然后在调用resolve时更改为“completed”,在调用reject时更改为“rejected”。

result——最初未定义,然后在调用resolve(value)时更改为value,在调用reject(error)时更改为error。

因此执行人最终将promise移动到以下状态之一:

稍后我们将看到“粉丝”如何订阅这些变化。

下面是一个promise构造函数和一个简单的executor函数,它的“生成代码”需要花费时间(通过setTimeout):

let promise = new Promise(function(resolve, reject{
  // the function is executed automatically when the promise is constructed

  // after 1 second signal that the job is done with the result "done"
  setTimeout(() => resolve("done"), 1000);
});

运行上面的代码,我们可以看到两件事:

执行程序被自动且立即地(通过new Promise)调用。

执行器接收两个参数:resolve和reject。这些函数是由JavaScript引擎预先定义的,所以我们不需要创建它们。我们准备好了就叫他们其中一个。

在一秒钟的“处理”之后,执行程序调用resolve(“完成”)来生成结果。这会改变promise对象的状态:

这是一个成功完成工作的例子,一个“fulfilled prommise”。

下面是一个 Promise 执行人用错误 reject promise 的例子:

let promise = new Promise(function(resolve, reject{
  // after 1 second signal that the job is finished with an error
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

reject(…)的调用将promise对象移动到“rejected”状态:

总而言之,执行者应该执行一项工作(通常需要花费时间),然后调用resolve或reject来更改相应promise对象的状态。

被解决或被拒绝的承诺称为“已解决”,而不是最初的“待解决”承诺。

执行程序应该只调用一个resolve或一个拒绝。任何状态的改变都是最终的。

所有进一步的resolve和reject调用都被忽略:

let promise = new Promise(function(resolve, reject{
  resolve("done");

  reject(new Error("…")); // ignored
  setTimeout(() => resolve("…")); // ignored
});

其思想是执行者完成的工作可能只有一个结果或一个错误。

同样,resolve/reject只期望一个参数(或none),并将忽略其他参数。

万一出了问题,遗嘱执行人应该调用reject。这可以用任何类型的参数来完成(就像resolve)。但是建议使用Error对象(或者从Error继承的对象)。这样做的理由很快就会变得显而易见。

在实践中,执行程序通常异步执行一些操作,并在一段时间后调用resolve/reject,但它并不需要这样做。我们也可以立即调用resolve或reject,像这样:

let promise = new Promise(function(resolve, reject{
  // not taking our time to do the job
  resolve(123); // immediately give the result: 123
});

例如,这可能发生在当我们开始做一个工作,但然后看到所有事情都已经完成和缓存。

这很好。我们立即有了一个解决的承诺。


浏览 26
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报