您当前的位置:首页 > 计算机 > 编程开发 > JavaScript

【翻译】promise

时间:12-23来源:作者:点击数:

原英语文档链接

javascript.info/promise-bas…

image.png

翻译

Promise

我们设想一下,假如你是一个歌星,你的粉丝总是不分昼夜的向你询问你将要完成的歌曲(注:就像我们总是问杰伦,什么时候发新歌)

为了减轻这种焦虑,你向粉丝们保证,一旦发新歌,马上就告诉他们,然后呢你就给他们一张表,让他们填上他们的邮箱地址,便于及时地告知你的新歌情况,即使遇到非常糟糕的事情,例如,你的录音室着火了,不能发歌,你的粉丝也会被通知到位。

每个人都很开心,一方面因为你的粉丝们不会在逼逼叨叨你,另一方面粉丝再也不会错过你的歌。

与现实中的这种类似的事情相比,在编程中我们也会经常遇到:

  1. “producing code” 是耗时做些事情的代码。例如用来通过网络加载数据的代码。 这代表一个歌手
  2. “consuming code” 是想要“producing code”的结果的代码,一旦“producing code”把这些结果准备好。那这些“consuming code”可以看作是粉丝
  3. promise是一个特殊的java script对象,它把“producing code”和“consuming code”联系了起来,类比一下,这个就是那个通知粉丝的邮箱地址表。“producing code”需要花费时间来产出承诺的结果,什么时候准备好了,“promise”就会把这些结果通知给对应的code。

这个对比其实很不精确,在java script中,promise其实更复杂,它有更多的特性和限制。好了,让我们言归正传吧。

创建promise 对象实例的语法如下:

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

传递给new Promise的函数称为executor。一旦new Promise被创建,executor就会立即执行。它包含最终生成结果的生成代码。根据上面的类比:executor就是“歌手”。

这个函数的参数resolve 和reject是JavaScript本身提供的回调。我们的代码只在executor内部

当executor获得结果时,无论是快还是晚,都无所谓,它应该会调用以下回调之一:

  • resolve(value)-- 当执行成功,并且有返回值value
  • reject(error)-- 当执行失败,产生错误

综述,当executor运行去尝试完成某项任务时,执行完之后就可能会产生两个结果,执行成功就去调用resolve, 执行失败就去调用reject,产生error。

通过new promise方法创建的promise对象具有以下内部属性

  • state -- 初始是pending状态,当调用resolve时,就跳转到fulfilled, 当调用reject,就跳转到rejected
  • result -- 初始值是undefined,当调用resolve时,值就变成了value, 当调用reject,值就是error
image.png

后面我们将会了解 “粉丝”是怎么知道这些变化的。 下面是一个promise构造函数和一个简单的执行器函数的示例,其中“生成代码”需要时间(通过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); 
});

通过执行以上代码,我们可以了解两件事情

  1. 通过new Promise, executor自动立即被调用
  2. executor接收resolve和reject这两个参数,resolve和reject是javascript引擎预先定义好的,所以不需要我们去创建它们。我们只需当executor准备好,调用其中一个就可以。

经过一秒的处理之后,executor就会调用resolve(“done”)返回结果。这也改变了promise对象的状态

image.png

这是一个成功完成的例子,一个“fulfilled promise”的例子

现在举一个executor拒绝promise,带有error的例子

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" 状态

image.png

简而言之,executor应该执行一项任务(通常是些需要花费时间的事情),然后去调用 resolve或者reject,promise 对象就会随之改变相应的状态。

一个promise状态不管是resolved状态还是rejected状态,都统一被叫做settled, 与之相反的就是初始的pending状态

There can be only a single result or an error

executor只能调用resolve或reject一次,然后返回一个最终的状态

所有后续的resolve或reject调用都会被忽略:

let promise = new Promise(function(resolve, reject) { 
resolve("done");
reject(new Error("…")); // ignored 
setTimeout(() => resolve("…")); // ignored 
});

这个意思就是说,当executor执行完一个job,就会返回一个结果或者是一个error。 另一方面, resolve/reject也只0个或一个参数,额外的参数也会被忽略。

Reject with Error objects

万一出了什么问题,executor就会调用reject。这可以返回任何类型的值(比如resolve) 但是我们建议使用error对象(或者是继承error的对象),为啥这样做后面很快就明白了。

Immediately calling resolve/reject

实际上,executor通常以异步方式执行某些操作,然后经过一段时间去调用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
});

举个例子,当我们开始做一项工作,但看到所有的事情都已经完成并缓存时,可能会发生这种情况。这样是OK的,我们会立即得到一个resolved promise

The state and result are internal

Promise对象的属性state和 result 是内部的。我们无法直接访问它们。我们可以使用这些方法.then/.catch/.finally来访问。下文将对其进行说明。

Consumers: then, catch, finally

一个promise 对象充当 executor(producing code 或者 歌手)和 consuming functions(粉丝)之间的桥梁,它将会接收result 或者error。consuming functions就会被注册(订阅)使用.then, .catch and .finally 方法.

then

最重要、最基本的一个就是.then 语法如下:

promise.then( 
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ } 
);

.then的第一个参数是一个方法,这个方法是当promise是resolved,并且返回了result。

.then的第二个参数是一个方法,这个方法是当promise是rejected,并且返回了error。

举一个成功返回resolved promise的例子

let promise = new Promise(function(resolve, reject) { 
setTimeout(() => resolve("done!"), 1000); 
}); 
// resolve runs the first function in .then 
promise.then( 
  result => alert(result), // shows "done!" after 1 second
  error => alert(error) // doesn't run 
);

.then中第一个方法将会执行

再举一个关于rejection的例子:

let promise = new Promise(function(resolve, reject) { 
   setTimeout(() => reject(new Error("Whoops!")), 1000); 
}); 
// reject runs the second function in .then 
promise.then( 
result => alert(result), // doesn't run 
error => alert(error) // shows "Error: Whoops!" after 1 second 
);

如果我们只对成功的执行操作感兴趣,那么在.then方法中,我们可以只传入一个参数:

let promise = new Promise(resolve => { 
setTimeout(() => resolve("done!"), 1000);
});

promise.then(alert); // shows "done!" after 1 second

catch

如果我们只关注errors, 那么我们可以设置第一个参数为null : .then(null, errorHandlingFunction)

或者我们可以使用.catch(errorHandlingFunction), 就像下面这样:

let promise = new Promise((resolve, reject) => { 
setTimeout(() => reject(new Error("Whoops!")), 1000); 
}); 
// .catch(f) is the same as promise.then(null, f) 
promise.catch(alert); // shows "Error: Whoops!" after 1 second

.catch(f)和.then(null, f)的功能是一样的,只是.catch(f)更简略一些

finally

正如try {...} catch {...}语句有个finally一样,在promises里也有一个finally

.finally(f)和.then(f, f)很相似, 这个f不管promise是resolved还是reject,只要是settled,都会执行

finally是执行清理操作一个很好的方式,例如终止我们的加载指示器,当我们不在需要它们,无论结果怎么样。就像这样:

new Promise((resolve, reject) => { 
/* do something that takes time, and then call resolve/reject */ 
}) 
// runs when the promise is settled, doesn't matter successfully or not 
.finally(() => stop loading indicator) // so the loading indicator is always stopped before we process the result/error
.then(result => show result, err => show error)

意思是说,虽然finally(f)并不完全是then(f,f)的别名,但两者之间几乎没有细微的差别。

  1. finally 没有任何参数. 在finally里, 我们不知道promise是成功还是失败。但是没有没关系,因为我们的任务通常会去执行“general”来确定最终的结果.
  2. finally 方法会把results和errors传递给下一个处理方法,像这样,finally把result传递给了then:
new Promise((resolve, reject) => { 
setTimeout(() => resolve("result"), 2000) }) 
.finally(() => alert("Promise ready")) 
.then(result => alert(result)); // <-- .then handles the result

如果promise有error,finally就会把error传给catch处理:

new Promise((resolve, reject) => { 
throw new Error("error"); 
}) 
.finally(() => alert("Promise ready")) 
.catch(err => alert(err)); // <-- .catch handles the error object

这样的话就非常方便,因为finally并不是用来处理promise结果的,所以它只需把结果传递出去。

在下一章节中,我们将更多地讨论promise chainning 和 方法之间的result-passing。

We can attach handlers to settled promises

如果promise是pending状态,.then/catch/finally这些方法就待命,否则一旦promise是settled状态,这些方法就会执行:

// the promise becomes resolved immediately upon creation 
let promise = new Promise(resolve => resolve("done!")); 
promise.then(alert); // done! (shows up right now)

注意,这与现实生活中的邮箱分发列表相比,promises更有效率。当一个歌手已经发表了他的歌,这时有个人再去那个邮箱列表填上自己的邮箱,就有可能会收不到这首歌。因为在现实生活中,当歌完成后发送信息这个动作在这个人去邮箱列表增加邮箱这个事件之前,

promise要更加灵活,我们可以在任意时间添加处理方法:只要result准备好,这些方法就会被执行

接下来,让我们参考更多的实际例子,来帮助我们更好的了解promise是怎么帮助我们写异步代码的

Example: loadScript

从先前的章节中,我们已经了解loadScript方法是用来加载一个脚本

以下是基于回调的变形,只是提醒我们

function loadScript(src, callback) { 
let script = document.createElement('script'); 
script.src = src; 
script.onload = () => callback(null, script); 
script.onerror = () => callback(new Error(`Script load error for ${src}`)); 
document.head.append(script); }

接下来让我们用promises来重写它 创建一个loadScript方法将不需要回调。相反,它将会创建并且返回一个promise对象,这个promise对象解决这个加载什么时候完成。外部的代码可以用.then给它添加方法(subscribing functions)

function loadScript(src) { 
    return new Promise(function(resolve, reject) { 
        let script = document.createElement('script'); 
        script.src = src; 
        script.onload = () => resolve(script); 
        script.onerror = () => reject(new Error(`Script load error for ${src}`)); 
        document.head.append(script); 
    }); 
}

使用:

let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"); 
promise.then( 
script => alert(`${script.src} is loaded!`), error => alert(`Error: ${error.message}`) 
); 
promise.then(script => alert('Another handler...'));

与基于回调的模式相比,我们可以立即看到一些好处:

Promises Callbacks
Promises 让我们按照自然的顺序做事. 首先, 我们执行 loadScript(script), 然后.then 去处理result. 当调用loadScript(script, callback)时,我们必须要有一个callback方法在我们的处置中。换句话说, 我们必须知道怎么处理result在loadScript被调用之前.
只要我们愿意,我们就可以调用promise的.then 方法任意次. 每次调用的时候, 我们就是在“subscription list”上增加了一个新的粉丝,一个新的发送动作,更多内容可以看下个章节 : Promises chaining. 只能调用一次.

因此,promise给了我们更好的代码流和灵活性。但还有更多。我们将在下一章中看到这一点。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐