Promise是一个JavaScript标准内置对象。用来存储一个异步任务的执行结果,以备未来使用。javascript
建立一个Promise对象:java
let promise = new Promise(function(resolve, reject) {
// executor
});
复制代码
构造函数Promise接收一个函数(称为执行器executor)做为参数,并向函数传递两个函数做为参数:resolve和reject。git
建立的Promise实例对象(如下用promise代替)具备如下内部属性:github
当执行new Promise时,executor会当即执行。能够在里面书写须要处理的异步任务,同步任务也支持。 任务处理完获得的结果,须要调用如下回调之一:json
resolve/reject只须要一个参数(或不包含任何参数),多余会被忽略。api
一个已经“settled”的promise(状态已经变为"fulfilled"或"rejected")将不能再次调用resolve或reject,即resolve或reject只能调用一次。剩下的resolve和reject的调用都会被忽略。数组
示例:promise
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done"), 1000);
});
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
复制代码
经过.then、.catch和.finally来消费promise。浏览器
promise的state和result属性都是内部的,没法直接访问它们。 但咱们可使用 .then/.catch/.finally 来访问。app
.then(f, f) 接收两个函数参数: - 第一个函数在promise resolved后执行并接收结果做为参数; - 第二个函数(非必填)在promise rejected后执行并接收error做为参数;
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(
result => alert(result), // 1 秒后显示 "done!"
error => alert(error) // 不运行
);
复制代码
.catch(f) 接收一个函数做为参数,该函数接收reject的error做为参数,是.then(null, f)的简写形式。
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
promise.catch(alert); // 1 秒后显示 "Error: Whoops!"
复制代码
.finally(f) 接收一个无参数的函数,不论promise被resolve或reject都会执行。
new Promise((resolve, reject) => {
// ...
}).finally(() => stop loading indicator)
.then(result => show result, err => show error);
复制代码
finally会继续将promise的结果传递下去。
.then/.catch/.finally异步执行:会等到promise的状态由pending变为settled时,当即执行。
更确切地说,当 .then/catch 处理程序应该执行时,它会首先进入内部队列。JavaScript 引擎从队列中提取处理程序,并在当前代码完成时执行 setTimeout(..., 0)。
换句话说,.then(handler) 会被触发,会执行相似于 setTimeout(handler, 0) 的动做。
在下述示例中,promise 被当即 resolved,所以 .then(alert) 被当即触发:alert 会进入队列,在代码完成以后当即执行。
// 一个被当即 resolved 的 promise
let promise = new Promise(resolve => resolve("done!"));
promise.then(alert); // done!(在当前代码完成以后)
alert("code finished"); // 这个 alert 会最早显示
复制代码
所以在 .then 以后的代码老是在处理程序以前被执行(即便是在预先 resolved 的 promise 的状况下)。
promise.then(f)的处理程序f(handler)调用后返回一个promise,handle自己返回的值会做为这个promise的result;result能够传递给下一个.then处理程序链进行传递。 对同一个promise分开.then时,每一次的结果都同样,由于.then只是单纯使用了promise提供的result,并不改变原来的promise自己。
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
复制代码
.then(handler) 中所使用的处理程序(handler)能够建立并返回一个 promise。 此时其余的处理程序(handler)将等待它 settled 后再得到其result
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
复制代码
handler也能够返回一个“thenable” 对象 —— 一个具备方法 .then 的任意对象。它会被当作一个 promise 来对待。 第三方库能够实现本身的promise兼容对象。它们能够具备扩展的方法集,但也与原生的 promise 兼容,由于它们实现了 .then 方法。
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// 1 秒后使用 this.num*2 进行 resolve
setTimeout(() => resolve(this.num * 2), 1000); // (**)
}
}
new Promise(resolve => resolve(1))
.then(result => {
return new Thenable(result); // (*)
})
.then(alert); // 1000ms 后显示 2
复制代码
handler返回的对象若是具备then方法,会被当即调用并接收resolve和reject做为参数。直到resolve或reject执行后,再将result传递给下一个.then,以此类推,沿着链向下传递。
这个特性能够用来将自定义的对象与 promise 链集成在一块儿,而没必要继承自 Promise。 推荐异步行为始终返回一个promise以便后续对链进行扩展。
若是 .then(或 catch)处理程序(handler)返回一个 promise,那么链的其他部分将会等待,直到它状态变为 settled。当它被 settled 后,其 result(或 error)将被进一步传递下去。
比较promise.then(f1, f2);
和promise.then(f1).catch(f2);
Promise类有5种静态方法:
这五个方法中,Promise.all 在实战中使用的最多。
let promise = Promise.resolve(value)
—— 根据给定的 value 值返回 resolved promise。 等价于:let promise = new Promise(resolve => resolve(value));
let promise = Promise.reject(error)
—— 建立一个带有 error 的 rejected promise。 等价于:let promise = new Promise((resolve, reject) => reject(error));
let promise = Promise.all([...promises...]);
—— 并行执行多个promise,返回一个新的promise,其结果为包含全部promise的结果的有序数组。参数为一个promise数组(严格能够是任何可迭代对象,一般是数组)。
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 1,2,3 当 promise 就绪:每个 promise 即成为数组中的一员
// 经常使用来发送并行请求
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => {
// 全部响应都就绪时,咱们能够显示 HTTP 状态码
for(let response of responses) {
alert(`${response.url}: ${response.status}`); // 每一个 url 都显示 200
}
return responses;
})
// 映射 response 数组到 response.json() 中以读取它们的内容
.then(responses => Promise.all(responses.map(r => r.json())))
// 全部 JSON 结果都被解析:“users” 是它们的数组
.then(users => users.forEach(user => alert(user.name)));
复制代码
若是任意一个 promise为reject,Promise.all返回的 promise 就会当即 reject 这个错误。并忽略全部列表中其余的 promise。它们的结果也被忽略。
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: Whoops!
复制代码
Promise.all(...) 接受可迭代的 promise 集合(大部分状况下是数组)。可是若是这些对象中的任意一个不是 promise,它将会被直接包装进 Promise.resolve。
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}),
2, // 视为 Promise.resolve(2)
3 // 视为 Promise.resolve(3)
]).then(alert); // 1, 2, 3
复制代码
用法同Promise.all,只不过Promise.allSettled会等待全部的 promise 都被处理:即便其中一个 reject,它仍然会等待其余的 promise。处理完成后的数组由如下对象组成: {status:"fulfilled", value:result}
对于成功的响应, {status:"rejected", reason:error}
对于错误的响应。
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://no-such-url'
];
Promise.allSettled(urls.map(url => fetch(url)))
.then(results => {
/* results: [ {status: 'fulfilled', value: ...response...}, {status: 'fulfilled', value: ...response...}, {status: 'rejected', reason: ...error object...} ]*/
results.forEach((result, num) => {
if (result.status == "fulfilled") {
alert(`${urls[num]}: ${result.value.status}`);
}
if (result.status == "rejected") {
alert(`${urls[num]}: ${result.reason}`);
}
});
});
复制代码
若是浏览器不支持 Promise.allSettled,使用 polyfill 很容易让其支持:
if(!Promise.allSettled) {
Promise.allSettled = function(promises) {
// p => Promise.resolve(p) 将该值转换为 promise(以防传递了非 promise)
return Promise.all(promises.map(p => Promise.resolve(p).then(
v => ({ state: 'fulfilled', value: v }),
r => ({ state: 'rejected', reason: r })
)));
};
}
复制代码
let promise = Promise.race(iterable);
—— 与 Promise.all 相似,它接受一个可迭代的 promise 集合,可是只要有一个promise被settled了就会中止等待,将这个promise的结果/错误做为它的结果,其余的promise的结果/错误都会被忽略。
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
复制代码
try..catch
。// 1. 代码执行错误
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!
// 1. 主动调用reject
new Promise((resolve, reject) => {
reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!
// 2. 处理器中的错误
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
throw new Error("Whoops!"); // rejects 这个 promise
}).then((result) => {
// 这个then不执行
}).catch(alert); // Error: Whoops!
// 3. 执行:catch -> then
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) {
alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
复制代码
若是promise变为rejected,可是却没有catch处理这个错误,错误就被卡住(stuck),脚本就会死掉。
JavaScript引擎会跟踪此类rejections,生成一个全局错误,能够监听unhandledrejection
事件捕获。
window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // [object Promise] - 产生错误的 promise
alert(event.reason); // Error: Whoops! - 未处理的错误对象
});
new Promise(function() {
throw new Error("Whoops!");
}); // 没有 catch 处理错误
复制代码
建议将.catch放在想要处理错误的位置,自定义错误类来帮助分析错误,还能够从新抛出错误。 若是发生错误后没法恢复脚本,那不用catch处理错误也行,可是应该使用unhandledrejection
事件来跟踪错误。 使用finally处理必需要发生的任务,好比关闭loading。
有一个浏览器技巧是从 finally 返回零延时(zero-timeout)的 promise。这是由于一些浏览器(好比 Chrome)须要“一点时间”外的 promise 处理程序来绘制文档的更改。所以它确保在进入链下一步以前,指示在视觉上是中止的。
在setTimeout中抛出的错误没法被catch捕获:
const promise = new Promise(function(resolve, reject) {
setTimeout(function () { throw new Error('test') }, 0)
});
promise.catch(function(error) { console.log(error) });
复制代码
除非显式调用reject:
const promise = new Promise(function(resolve, reject) {
setTimeout(function () {
reject(new Error('test'));
}, 0)
});
复制代码
缘由:JS事件循环列表有宏任务与微任务之分:setTimeOut是宏任务, promise是微任务,他们有各自的执行顺序;所以这段代码的执行顺序是:
hrow new Error('test')
此时这个异常实际上是在promise外部抛出的 但若是在setTimeOut中主动触发了promise的reject方法,所以promise的catch将会在setTimeOut回调执行后的属于他的微任务队列中找到它而后执行,因此能够捕获错误Promise 的处理程序(handlers).then、.catch 和 .finally 都是异步的。 异步任务须要适当的管理。为此,JavaScript 标准规定了一个内部队列 PromiseJobs —— “微任务队列”(Microtasks queue)(v8 术语)。 这个队列先进先出,只有引擎中没有其余任务运行时才会启动任务队列的执行。 当一个 promise 准备就绪时,它的 .then/catch/finally 处理程序就被放入队列中。等到当前代码执行完而且以前排好队的处理程序都完成时,JavaScript引擎会从队列中获取这些任务并执行。 即使一个 promise 当即被 resolve,.then、.catch 和 .finally 以后的代码也会先执行。 若是要确保一段代码在 .then/catch/finally 以后被执行,最好将它添加到 .then 的链式调用中。
let promise = Promise.resolve();
promise.then(() => alert("promise done"));
alert("code finished"); // 该警告框会首先弹出
复制代码
指在 microtask 队列结束时未处理的 promise 错误。 microtask队列完成时,引擎会检查promise,若是其中任何一个出现rejected状态,就会触发unhandledrejection事件。 但若是在setTimeout里进行catch,unhandledrejection会先触发,而后catch才执行,因此catch没有发挥做用。
let promise = Promise.reject(new Error("Promise Failed!"));
setTimeout(() => promise.catch(err => alert('caught')));
window.addEventListener('unhandledrejection', event => alert(event.reason));
// Promise Failed! -> caught
复制代码
简单的示例
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);
}
// promise改写:
let loadScriptPromise = function(src) {
return new Promise((resolve, reject) => {
loadScript(src, (err, script) => {
if (err) reject(err)
else resolve(script);
});
})
}
// 用法:
// loadScriptPromise('path/script.js').then(...)
复制代码
通用的promisify函数:
function promisify(f) {
return function (...args) { // 返回一个包装函数
return new Promise((resolve, reject) => {
function callback(err, result) { // 给 f 用的自定义回调
if (err) {
return reject(err);
} else {
resolve(result);
}
}
args.push(callback); // 在参数的最后附上咱们自定义的回调函数
f.call(this, ...args); // 调用原来的函数
});
};
};
// 用法:
let loadScriptPromise = promisify(loadScript);
loadScriptPromise(...).then(...);
复制代码
以上回调函数只能接收两个参数,接收多个参数的示例:
// 设定为 promisify(f, true) 来获取结果数组
function promisify(f, manyArgs = false) {
return function (...args) {
return new Promise((resolve, reject) => {
function callback(err, ...results) { // 给 f 用的自定义回调
if (err) {
return reject(err);
} else {
// 若是 manyArgs 被指定值,则 resolve 全部回调结果
resolve(manyArgs ? results : results[0]);
}
}
args.push(callback);
f.call(this, ...args);
});
};
};
// 用法:
f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...)
复制代码