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 来访问。markdown
.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 => ...) 复制代码