什么是Promise
Promise中文意为"诺言",现在对它的历史解析开始了。
JavaScript是单线程的,调用函数执行一个异步操作的时候,通常会塞给这个函数几个callback回调函数,这个函数在未来自主选择在何时调用、调用哪个、传递什么参数。
那如果你不先确定好要如何编写这个callback(比如你还没想清楚要如何使用回调时的参数),你就无法调用这个函数,直到你想清楚了,写好了,你才能调用它,任务才会开始。
一旦你不在callback中作出决定,就调用了这个函数,那么你将永远失去你可以获得的result。
我们用同步来模拟异步,看看其中有什么乾坤
const child_process = require('child_process');
// 模拟一次耗时2s的操作, 得到结果8
function await(millis) {
child_process.execSync(`sleep ${ millis / 1000 };`);
return 8;
}
function awaitResult(callback) {
console.log('开始异步任务');
var result = await(2000);
callback(result);
console.log('完成异步任务');
}
console.log('开始调用异步函数');
awaitResult(result => {
console.log('结果是' + result);
});
console.log('完成调用异步函数');
/**
开始调用异步函数
开始异步任务
(...2s后)
结果是8
完成异步任务
完成调用异步函数
*/
而我们改用Promise来重写它以后:
const child_process = require('child_process');
// 模拟一次耗时2s的操作, 得到结果8
function await(millis) {
child_process.execSync(`sleep ${ millis / 1000 };`);
return 8;
}
function awaitResult() {
var task = new Promise((resolve, reject) => {
console.log('开始异步任务'); // Promise构造函数会立即执行这个函数
var result = await(2000);
resolve(result);
});
console.log('完成异步任务');
return task;
}
console.log('开始调用异步函数');
var task = awaitResult();
console.log('完成调用异步函数');
task.then(result => {
console.log('结果是' + result);
});
/**
开始调用异步函数
开始异步任务
(...2s后)
完成异步任务
完成调用异步函数
结果是8
*/
相信大家一定可以看出来了,"诺言"的含义:
立刻执行这个任务!成功的话我等一下会告诉你你接下来该怎么办的,把结果给我留好,随时待命。对了,失败的话我也会告诉你该怎么办的,你先做,做完好好等着就是了,说不定你还没做完我就已经告诉你该怎么弄了。
调用者(程序员)承诺:会告诉执行者如何处理result。(! 这个操作是可以重复的)
执行者(Promise)承诺:会在未来某个时机执行调用者给出的函数,当然了,人家最爱的是result,传递给他。
Promise 的承诺
Promise是在何时承诺的呢?关键就在于下面这行代码,resolve
和reject
就是Promise给我们的承诺之星,分别对应成功和失败两种状态,用then()
和catch()
来回应。
resolve(result);
Promise 的状态
Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。
- 异步操作未完成(pending)
- 异步操作成功(fulfilled)
- 异步操作失败(rejected)
上面三种状态里面,fulfilled和rejected合在一起称为resolved(已定型)。
这三种的状态的变化途径只有两种。
- 从“未完成”到“成功”
- 从“未完成”到“失败”
一旦状态发生变化,就凝固了,不会再有新的状态变化。
这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。
当然了,对于这种说法本人不置可否。就连MDN英文也表示:JavaScript中的承诺表示已经发生的进程,可以与回调函数链接在一起。
因此,Promise 的最终结果只有两种。
- 异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。
- 异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。
Promise 对象、Promise 对象状态、承诺
现在我们再次强化这种认知:
一个Promise对象被new后立即执行构造时用户传递的函数,这个函数的原型为:(resolve, reject) => {}
,在调用resolve()
和reject()
之前,Promise处于pending状态,直到有且仅有其中一个状态函数被调用,Promise对象状态被改变,成为resolved状态,及fulfilled或rejected状态之一。
resolve(...param)
导致Promise进入fulfilled状态,对Promise # then((...param) => {})
的每次调用都将得到承诺被回调,并且传递对应的param。成功状态下的Promise # catch()
调用就像被忽略了一样。
reject(...param)
导致Promise进入rejected状态,对Promise # catch((...param) => {})
的每次调用都将得到承诺被回调,并且传递对应的param。then()的处理请看链式调用。
链式调用
容易迷糊的地方来了! then()和catch()也返回新的Promise对象!
我们需要研究then、catch的机制。
我们先看看一个链式调用的例子:
"use strict";
function a(state) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (state)
resolve('Task A succeed');
reject('Task A failed');
}, 800);
});
}
function b(state) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (state)
resolve('Task B succeed');
reject('Task B failed');
}, 800);
});
}
{
// a进入成功状态,then参数r=>{}被执行,then返回的Promis对象也进入成功状态
a(true).then(r => {
console.log('[A]', r); // 800ms后输出:[A] task A succeed
});
}
{
// a进入成功状态,catch参数r=>{}不被执行,catch返回的Promise对象直接进入成功状态
a(true).catch(r => {
console.log('[B]', r); // 不会打印
}).then(r => {
console.log('[B2]', r);
});
}
{
// a进入成功状态,该对象没有then方法,没有任何处理
a(true);
// a进入失败状态,该对象没有一个catch方法,抛出UnhandledPromiseRejectionWarning异常,不是这个调用抛出的,是Promise内部抛出的
// a(false); // 会导致异常
// 给会失败的a一个catch处理方法,UnhandledPromiseRejectionWarning异常消失
a(false).catch(e => {
console.log('[C]', e);
});
// 在a进入失败状态之前就应该设置catch方法,否则照样报异常
if (false) {
let tmp = a(false);
setTimeout(() => {
tmp.catch(r => {
console.log('[C2]', r);
});
}, 1000);
}
// 失败状态的Promise不会回调then处理方法,不过then返回的Promise对象也进入失败状态,由于父子Promise都没有catch,抛出异常
if (false)
a(false).then(r => {
console.log('[C3]');
});
// then进入失败状态后调用catch处理方法,异常得到了处理,父Promise不抛出异常
a(false).then(r => {
console.log('[C4]');
}).catch(e => {});
// 失败的传递性
if (false) {
let tmp = a(false);
tmp.catch(() => {console.log('失败1');}); // 执行
tmp.then(() => {console.log('失败3');}).catch(() => {console.log('失败4');}); // tmp失败,但是异常传递给then并得到了catch处理
tmp.then(() => {console.log('失败2');}); // then失败没有catch,抛出异常
}
}
{
// a成功,执行then,由于then处理方法返回一个Promise,then仍然处于pending状态,直到b失败,then失败,下一个then失败,执行catch
a(true).then(r => b(false)).then(r => {
console.log('[D]', r);
}).catch(reason => {
console.log('失败原因:', reason);
});
}
- Promise1.then(r => use(r)).then(r => use(r)).catch(reason => use(reason));
对于Promise1对象的then调用,当Promise状态定型时,若
1.失败,则then返回的Promise2失败,...失败原因传递给最后一个then,如果没有被catch则抛出异常
2.成功,执行then处理方法,如果use(r)返回Promise对象,则等待该对象,这里发生了对象的代理,否则then成功,执行下一个then。
ES6 async
await
关键字
有了Promise还不够,我们可以使用async
关键字将一个function函数或者箭头函数声明为同步函数,对于一个返回Promise实例的函数调用,使用await关键字获取resolve()函数传递的值,仿佛编写同步代码一样,无需回调函数。
"use strict";
function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(888);
}, 800);
});
}
async function test() {
try {
var result = await getData();
console.log('computed result:', result);
} catch(e) {
console.log('An Error occurred');
}
}
(function main() {
test();
(async () => {
var n = await getData();
console.log('computed result:', n);
})();
})();
直接await new Promise();
也是可以的!
(async () => {
var n = await new Promise((resolve, reject) => {
resolve(88);
});
console.log('computed result:', n);
})();
对了,别忘了async函数也返回的是一个Promise对象。