-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Promise
- 原文:Promise
想象你是一个顶级歌手,你的粉丝每天都向你催促新的单曲。
为了安抚粉丝,你承诺一旦发行就会寄给他们。你给了张列表给粉丝。他们填上地址以便新歌发布所有订阅可以立即得到。并且如果发生状况以至无法发布,粉丝也能得到通知。
皆大欢喜:对于你来说不用再被催逼了;对于粉丝来说不会错过单曲。
类比真实生活事件,我们在编程中:
1."产品代码"执行需要时间。比如,加载一个远程脚本。这就对应的"单曲"。
2.当准备好时"消费代码"要得到结果。许多功能要得到结果,这就对应"粉丝"。
3.promise是一个特殊的JavaScript对象把它们连接到一起。这就对应"列表"。产品代码创造promise并把它给到它们以便订阅结果。
promise对象构造语法如下:
let promise = new Promise(function(resolve, reject) {
// executor (the producing code, "singer")
});传给new Promise的回调叫做执行者。当promise创建时,它会自动调用。它包括了"产品代码",最终将作为结果完成的。在上面类比的几个词中,执行者就是"单曲"。
得到的promise对象有几个内置属性:
state——初始状态是"pending",然后变为"fulfilled"或者"rejected"。result——一个随意的值,初始为undefined。
当执行者执行完毕,他会调用其中一个:
resolve(value)- 表明任务成功完成:state切换为"fulfilled"。result变为value。
reject(error)- 表明发生错误:state切换为rejected。result为error。
举个例子,把上面的内容串起来:
let promise = new Promise(function(resolve, reject) {
// the function is executed automatically when the promise is constructed
alert(resolve); // function () { [native code] }
alert(reject); // function () { [native code] }
// after 1 second signal that the job is done with the result "done!"
setTimeout(() => resolve("done!"), 1000);
});运行上面的代码我们能看到2件事情:
1.执行者通过new Promise自动并且立即调用。
2.执行者接受2个参数:resolve与reject-这2个函数来自Javascript引擎。不用自建,而执行者在准备就绪时要调用它们。
经过1秒钟,执行者调用resolve("done")产生以下结果:
这是成功的例子。
下面是一个执行者报错拒绝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);
});小结:执行者通常要花时间执行任务,然后调用resolve或者reject来改变响应promise对象的状态(state)。
promise处于resolved或者rejected称为"settled",相反的是"pending"状态。
- 只能是返回结果或者报错
执行者只能调用resolve或者reject。promise状态变更后就是最终状态了。
resolve或reject之后的调用将被忽略:
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // ignored
setTimeout(() => resolve("…")); // ignored
});执行者任务的完成只能有一个结果或者报错。在编程中,存在其它的数据结构允许许多"动态"的结果,例如流和队列。对比promises它们各有优劣。它们没被Javascript核心原生支持,并且缺乏某些promises提供的语言特征,让我们把焦点放回到promises上。
假如我们调用resolve/reject时传入更多参数-只有第一个有效,更多的参数将被忽略。
- 使用
Error对象reject
技术上来讲在调用reject是能像resolve一样传入任何类型的参数。但是推荐使用Error对象reject(或者是继承自Error对象)。原因下文将提及。
- 立即调用
resolve/reject
实际上执行者通常异步处理事情并且之后常会调用resolve/reject,但是却不必要。可以立即调用resolve或者reject,如下:
let promise = new Promise(function(resolve, reject) {
resolve(123); // immediately give the result: 123
});例如,当我们开始执行任务时就已经发生,然后看到所有的事情都已经完成。技术上来说这很好:我们立即就有了一个resolved promises。
state与result是内置的
promises对象的state和result属性是内置的。在我们的代码中虽然不能直接访问它们,但却可以使用.then/catch方法访问,详见下文。
"消费者":".then"与".catch"
promise对象提供了"产品代码"(执行者)与"消费功能"(想接受result/error)之间的连接。"消费功能"可以通过使用promise.then与promise.catch方法注册。
.then语法如下:
promise.then(
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ }
);第一个函数的参数在promises resolved后得到结果后运行,第二个则是在rejected获取错误后运行。
举例如下:
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
);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如果我们只对错误感兴趣,那么我们就用.then(null,function)或者使用别名为.catch(function)的方法:
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),不过只是一个变形。
- 确定的
promisesthen方法会立即执行
如果promise在pending,.then/catch程序直到获取结果才处理。否则,如果 promise已经确定,将会立即执行:
// an immediately resolved promise
let promise = new Promise(resolve => resolve("done!"));
promise.then(alert); // done! (shows up right now)这便利了工作——有时需要时间,有时要立即完成。这保证了不同的需求。
.then/catch的处理总是异步的
更准确来说,当.then/catch程序被执行时,首先进入内部的队列。Javascript引擎从队列获取程序,在当前代码完成后执行,类似于setTimeout(...,0)。
换句话说,当.then(hander)将被触发,代替setTimeout(handler,0)做些事情。
在下面的例子中promise立即resolved,所以.then(alert)马上触发:alert函数进入队列并且在代码完成后立即执行。
// an immediately resolved promise
let promise = new Promise(resolve => resolve("done!"));
promise.then(alert); // done! (right after the current code finishes)
alert("code finished"); // this alert shows first所以在.then后面的代码总是先执行(尽管在预完成的promises例子中)。通常这不重要,只是在某些场景中这会有问题。
下面让我们看看更多关于promises处理异步的实例。
实例: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 ` + src));
document.head.append(script);
}用promises重构。
新的loadScript函数不用回调。代替的是创建并返回一个promise对象用于加载完成的处理。外面可以通过.then调用:
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: " + src));
document.head.append(script);
});
}用法:
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js");
promise.then(
script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`)
);
promise.then(script => alert('One more handler to do something else!'));我们能立即看出基于回调语法的好处:
-
Callbacks
- 必须准备好
callback当我们调用loadScript时。换句话说,在loadScript调用前必须知道该做什么的结果。 - 只能有一个回调。
- 必须准备好
-
Promises
promises能让我们以很自然的顺序去用代码写东西。首先我们执行loadScript,然后.then写入在获得结果后要做的事情。- 我们可以使用
promise调用.then任意次并且在之后的任意时间只要我们想。
所以promises已经让我们的代码更流畅和更灵活。但是还有更多美妙之处,在下一章节让我们拭目以待。
任务
再次resolve promise?
let promise = new Promise(function(resolve, reject) {
resolve(1);
setTimeout(() => resolve(2), 1000);
});
promise.then(alert);- 答案
输出:1。
第二次调用`resolve`会被忽略,因为只考虑第一次的`reject/resolve`,之后将被忽略。
使用promise延迟
内建函数setTimeout使用回调。造个promise替代。
函数delay(ms)需要返回一个promise。promise将在ms毫秒后resolve以便我们使用.then。如下:
function delay(ms) {
// your code
}
delay(3000).then(() => alert('runs after 3 seconds'));- 答案
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
delay(3000).then(() => alert('runs after 3 seconds'));
请注意此任务`resolve`未传参调用。`delay`后我们不用返回任何值,只是确认延迟。用promise画圈圈...
重构上一章的任务中showCircle函数,以便返回一个promise代替接受一个回调。
新的用法:
showCircle(150, 150, 100).then(div => {
div.classList.add('message-ball');
div.append("Hello, world!");
});基于上一章的任务的解决方案。


