一、在Promise类中,
- 有5类静态方法:Promise.resolve()、Promise.reject()、Promise.all()、Promise.allSettled()、Promise.race()。
- 2类动态方法:Promise.any()、Promise.try()
Promise方法
一、在现代的代码中,很少需要使用Promise.resolve和Promise.reject方法,因为async/await语法使它们变得有些过时了。
Promise.all()
一、等待所有 promise 都 resolve 时,返回存放它们结果的数组。如果给定的任意一个 promise 为 reject,那么它就会变成Promise.all的 error,所有其他 promise 的结果都会被忽略。
二、Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.all([p1, p2, p3]);
1、Promise.all()
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise 实例,如果不是,就会调用Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。
2、Promise.all()
方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
3、p
的状态由p1
、p2
、p3
决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
三、示例
【示例1】
let promise = Promise.all([1, 2, 3]);
promise.then(value => {
console.log(value); // [1,2,3]
});
console.log(promise);
1、promise结果:
【示例2】
let p2 = Promise.reject(2);
let promise = Promise.all([1, p2, 3]);
promise
.then(value => {
console.log(value);
})
.catch(err => {
console.log(err); // 2
});
console.log(promise);
1、promise结果:
【示例3】 Promise.all 在 3 秒之后被 settled,然后它的结果就是一个 [1, 2, 3] 数组
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 都贡献了数组中的一个元素
1、请注意,结果数组中元素的顺序与其在源 promise 中的顺序相同。即使第一个 promise 花费了最长的时间才 resolve,但它仍是结果数组中的第一个。
【示例4】常见技巧:将一个任务数据数组映射(map)到一个 promise 数组,然后将其包装到 Promise.all。如:如果我们有一个存储 URL 的数组,我们可以像这样 fetch 它们:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// 将每个 url 映射(map)到 fetch 的 promise 中
let requests = urls.map(url => fetch(url));
// Promise.all 等待所有任务都 resolved
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));
【示例5】通过 GitHub 用户名来获取一个 GitHub 用户数组中用户的信息(我们也可以通过商品 id 来获取商品数组中的商品信息,逻辑都是一样的)
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => {
// 所有响应都被成功 resolved
for(let response of responses) {
alert(`${response.url}: ${response.status}`); // 对应每个 url 都显示 200
}
return responses;
})
// 将响应数组映射(map)到 response.json() 数组中以读取它们的内容
.then(responses => Promise.all(responses.map(r => r.json())))
// 所有 JSON 结果都被解析:"users" 是它们的数组
.then(users => users.forEach(user => alert(user.name)));
【示例6】如果任意一个 promise 被 reject,由 Promise.all 返回的 promise 就会立即 reject,并且带有的就是这个 error。
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 实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法
const p1 = new Promise(resolve => {
resolve("hello");
})
.then(result => result)
.catch(e => e);
const p2 = new Promise(() => {
throw new Error("报错了");
})
.then(result => result)
.catch(e => e); // p2实际上是catch返回的promise实例
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
三、Promise.all(iterable)允许在iterable中使用 non-promise 的“常规”值
1、Promise.all(…)接受可迭代对象(iterable)的 promise(大多数情况下是数组)。但是,如果这些对象中的任意一个都不是 promise,那么它将被“按原样”传递给结果数组。
【示例1】这里的结果是[1, 2, 3]:
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}),
2,
3
]).then(alert); // 1, 2, 3
所以我们可以在方便的地方将准备好的值传递给Promise.all。
Promise.allSettled() :ES2020 新增方法
一、Promise.allSettled()
方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。
1、只有等到所有这些参数实例都返回结果,不管是fulfilled
还是rejected
,包装实例才会结束。
二、参数与Promise.all()
方法一样
let p2 = Promise.reject(2);
let promise = Promise.allSettled([1, p2, 3]);
promise.then(value => {
console.log(value); // [{status: "fulfilled", value: 1},{status: "rejected", reason: 2},{status: "fulfilled", value: 3}]
});
console.log(promise);
三、Promise.allSettled()
的返回的promise实例状态只可能变成resolved
,它的监听函数收到的参数是一个数组,该数组的每个成员都是一个对象,每一个对象都有status属性,该属性的值只可能是字符串fulfilled
或字符串rejected
。
1、fulfilled
时,对象有value
属性,rejected
时有reason
属性,对应两种状态的返回值。
2、结果数组:
(1){status:”fulfilled”, value:result} 对于成功的响应,
(2){status:”rejected”, reason:error} 对于 error。
四、示例
【示例1】我们想要获取(fetch)多个用户的信息。即使其中一个请求失败,我们仍然对其他的感兴趣
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.forEach((result, num) => {
if (result.status == "fulfilled") {
alert(`${urls[num]}: ${result.value.status}`);
}
if (result.status == "rejected") {
alert(`${urls[num]}: ${result.reason}`);
}
});
});
1、上面的 (*) 行中的 results 将会是
[
{status: 'fulfilled', value: ...response...},
{status: 'fulfilled', value: ...response...},
{status: 'rejected', reason: ...error object...}
]
2、对于每个 promise,我们都得到了其状态(status)和 value/reason
Polyfill
如果浏览器不支持Promise.allSettled,很容易进行 polyfill:
if (!Promise.allSettled) {
const rejectHandler = reason => ({ status: ‘rejected’, reason });
const resolveHandler = value => ({ status: ‘fulfilled’, value });
Promise.allSettled = function (promises) {
const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
return Promise.all(convertedPromises);
};
}
在这段代码中,promises.map获取输入值,并通过p => Promise.resolve(p)将输入值转换为 promise(以防传递了 non-promise),然后向每一个 promise 都添加.then处理程序(handler)。
这个处理程序(handler)将成功的结果value转换为{status:’fulfilled’, value},将 errorreason转换为{status:’rejected’, reason}。这正是Promise.allSettled的格式。
然后我们就可以使用Promise.allSettled来获取所有给定的 promise 的结果,即使其中一些被 reject。
Promise.any()
该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected
状态而结束。
let p1 = Promise.reject(1);
let p2 = Promise.reject(2);
let promise = Promise.any([p1, p2, 3]);
promise.then(value => {
console.log(value); // 3
});
console.log(promise);
当所有的实例返回的状态都是rejected
时,Promise.any()
会返回一个的实例状态才为rejected
let p1 = Promise.reject(1);
let p2 = Promise.reject(2);
let promise = Promise.any([p1, p2]);
promise
.then(value => {
console.log(value);
})
.catch(value => {
console.log(value); // AggregateError: All promises were rejected
});
console.log(promise);
Promise.race()
一、等待第一个 settle 的 promise,并将其 result/error 作为结果。
二、Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。Promise.race()
方法的参数与Promise.all()
方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()
方法,将参数转为 Promise 实例,再进一步处理。
let promise = Promise.race(iterable);
【示例1】
const p = Promise.race([p1, p2, p3]);
1、上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
三、与Promise.all类似,但只等待第一个 settled 的 promise 并获取其结果(或 error)。
【示例1】这里的结果将是1:
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
1、这里第一个 promise 最快,所以它变成了结果。第一个 settled 的 promise “赢得了比赛”之后,所有进一步的 result/error 都会被忽略。
Promise.reject()
一、Promise.reject(error)用error创建一个 rejected 的 promise
二、Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
Promise.resolve()
一、使用给定 value 创建一个 resolved 的 promise
二、有时需要将现有对象转为 Promise 对象,Promise.resolve()
方法就起到这个作用,且实例状态为resolve
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
Promise.resolve()方法的参数
参数是一个 Promise 实例
const promise = new Promise(resolve => {
resolve("resolve");
});
let p = Promise.resolve(promise);
console.log(p == promise); // true
参数是一个thenable
对象
一、thenable对象指的是具有then方法的对象,Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法
// thenable对象
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function (value) {
console.log(value); // 42
});
上面代码中,thenable
对象的then()
方法执行后,对象p1
的状态就变为resolved
,从而立即执行最后那个then()
方法指定的回调函数,输出42
参数不是具有then()
方法的对象,或根本就不是对象
【示例1】
const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s) // Hello
});
【示例2】下面的loadCached函数获取(fetch)一个 URL 并记住其内容。以便将来对使用相同 URL 的调用,它能立即从缓存中获取先前的内容,但使用Promise.resolve创建了一个该内容的 promise,所以返回的值始终是一个 promise。
let cache = new Map();
function loadCached(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // (*)
}
return fetch(url)
.then(response => response.text())
.then(text => {
cache.set(url,text);
return text;
});
}
1、我们可以使用loadCached(url).then(…),因为该函数保证了会返回一个 promise。我们就可以放心地在loadCached后面使用.then。这就是(*)行中Promise.resolve的目的。
不带有任何参数
一、Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象
Promise.resolve();
Promise.resolve();
// 相当于
new Promise(resolve => resolve(undefined))
Promise.try()(20220316-提案)
实际开发中,经常遇到一种情况:不知道或者不想区分,函数f
是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f
是否包含异步操作,都用then
方法指定下一步流程,用catch
方法处理f
抛出的错误。一般就会采用下面的写法。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
上面的写法有一个缺点,就是如果f
是同步函数,那么它会在本轮事件循环的末尾执行。鉴于这是一个很常见的需求,所以现在有一个提案,提供Promise.try
方法替代上面的写法。
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
消费者:then,catch,finally
一、Promise 对象充当的是 executor(“生产者代码”)和消费函数)之间的连接,后者将接收结果或 error。可以通过使用.then、.catch和.finally方法为消费函数进行注册。
catch()
一、如果我们只对 error 感兴趣,那么我们可以使用null作为第一个参数:.then(null, errorHandlingFunction)。或者我们也可以使用.catch(errorHandlingFunction),其实是一样的:
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// .catch(f) 与 promise.then(null, f) 一样
promise.catch(alert); // 1 秒后显示 "Error: Whoops!"
1、.catch(f)调用是.then(null, f)的完全的模拟,它只是一个简写形式。
Promise.prototype.catch()
一、catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
const promise = new Promise((_, reject) => {
reject("reject");
});
promise
.then(value => {
console.log(value);
})
// 发生错误,或者reject时执行
.catch(value => {
console.log(value);
});
二、如果 Promise 状态已经变成resolved,再抛出错误是无效的。
const promise = new Promise(resolve => {
resolve("resolve");
throw new Error("fail");
});
promise.then(value => console.log(value));
三、promise中所有没有被处理的错误都会冒泡到最后一个catch中
const promise = new Promise(resolve => {
resolve("resolve");
});
promise
.then(value => {
console.log(value);
throw new Error("fail1");
})
.then(() => {
throw new Error("fail2");
})
.catch(value => {
console.log(value);
});
在上面的代码中,catch会优先打印打印第一个错误,当第一个错误解决之后(注释掉就ok),catch里才会打印第二个错误 catch的返回值仍是promise,返回promise的方式和then相似,因此,catch后仍然可以调用then方法
finally()
一、就像常规try {…} catch {…}中的finally子句一样,promise 中也有finally。
二、.finally(f)调用与.then(f, f)类似,在某种意义上,f总是在 promise 被 settled 时运行:即 promise 被 resolve 或 reject。
三、finally是执行清理(cleanup)的很好的处理程序(handler),例如无论结果如何,都停止使用不再需要的加载指示符(indicator)。
像这样:
new Promise((resolve, reject) => {
/* 做一些需要时间的事儿,然后调用 resolve/reject */
})
// 在 promise 为 settled 时运行,无论成功与否
.finally(() => stop loading indicator)
// 所以,加载指示器(loading indicator)始终会在我们处理结果/错误之前停止
.then(result => show result, err => show error)
四、finally(f)其实并不是then(f,f)的别名。它们之间有一些细微的区别:
- finally处理程序(handler)没有参数。在finally中,我们不知道 promise 是否成功。没关系,因为我们的任务通常是执行“常规”的定稿程序(finalizing procedures)。
- finally处理程序将结果和 error 传递给下一个处理程序。例如,在这儿结果被从finally传递给了then:
在这儿,promise 中有一个 error,这个 error 被从finally传递给了catch:new Promise((resolve, reject) => { setTimeout(() => resolve("result"), 2000) }) .finally(() => alert("Promise ready")) .then(result => alert(result)); // <-- .then 对结果进行处理
这非常方便,因为finally并不是意味着要处理 promise 的结果。所以它将结果传递了下去。new Promise((resolve, reject) => { throw new Error("error"); }) .finally(() => alert("Promise ready")) .catch(err => alert(err)); // <-- .catch 对 error 对象进行处理
五、我们可以对 settled 的 promise 附加处理程序
1、如果 promise 为 pending 状态,.then/catch/finally处理程序(handler)将等待它。否则,如果 promise 已经是 settled 状态,它们就会运行: ```javascript // 下面这 promise 在被创建后立即变为 resolved 状态 let promise = new Promise(resolve => resolve(“done!”));
promise.then(alert); // done!(现在显示)
2、我们可以随时添加处理程序(handler):如果结果已经在了,它们就会执行。
<a name="QGFtf"></a>
#### Promise.prototype.finally()
一、finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。finally方法的回调函数不接受任何参数,这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
```javascript
const promise = new Promise(resolve => {
resolve("resolve");
});
promise.finally(() => {
console.log(11); // 11
});
二、finally的本质
promise.finally(() => {
// do something
});
// 等同于
promise.then(
result => {
// do something
return result;
},
error => {
// do something
throw error;
}
);
1、finally的返回值为一个新的和原来的值相似的promise
then()
一、语法如下:
promise.then(
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ }
);
1、.then的第一个参数是一个函数,该函数将在 promise resolved 后运行并接收结果。
2、.then的第二个参数也是一个函数,该函数将在 promise rejected 后运行并接收 error。
二、【示例1】以下是对成功 resolved 的 promise 做出的反应:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// resolve 运行 .then 中的第一个函数
promise.then(
result => alert(result), // 1 秒后显示 "done!"
error => alert(error) // 不运行
);
1、第一个函数被运行了。
2、在 reject 的情况下,运行第二个:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// reject 运行 .then 中的第二个函数
promise.then(
result => alert(result), // 不运行
error => alert(error) // 1 秒后显示 "Error: Whoops!"
);
3、如果我们只对成功完成的情况感兴趣,那么我们可以只为.then提供一个函数参数:
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(alert); // 1 秒后显示 "done!"
Promise.prototype.then()
一、then方法是定义在原型对象Promise.prototype上的,同时then方法的两个参数都是非必选的。因为then方法返回的是一个全新的promise实例时,因此then方法可以链式调用 then方法在以下情况返回的promise
1、当未传入参数时,then方法会返回一个新的,状态和原promise相同的promise
const promise = new Promise(resolve => {
resolve("resolve");
});
let p = promise.then();
console.log(promise);
console.log(p);
结果展示
2、上一个promise未被成功调用then方法时,返回的结果如情形1
const promise = new Promise((_, reject) => {
reject("reject");
});
let a = promise.then(value => {
console.log(value);
});
3、上一个promise被成功调用then方法时,返回一个resolve(undefined)
的promise
const promise = new Promise((_, reject) => {
reject("reject");
});
let a = promise.then(undefined, value => {
console.log(value);
});
console.log(a);
4、上一个promise被成功调用then方法,但出现报错,返回一个reject('error')
的promise,error
代指错误,并非真正的reject
返回的结果
const promise = new Promise(resolve => {
resolve("resolve");
});
let p = promise.then(value => {
console.log(value);
throw new Error("fail1");
});
console.log(p);
结果展示:
5、给then方法手动返回一个promise,此时会覆盖掉默认的行为,返回值为新增的promise
const promise = new Promise(resolve => {
resolve("resolve");
});
let p = promise.then(
() =>
new Promise(resolve => {
resolve("resolve2");
})
);
console.log(p);
一、对promise.then的调用会返回了一个 promise
一、then里的参数是可选的,catch(failureCallback)是then(null, failureCallback)的缩略形式。
| 【示例】```javascript doSomething().then(function(result) { return doSomethingElse(result); }) .then(function(newResult) { return doThirdThing(newResult); }) .then(function(finalResult) { console.log(‘Got the final result: ‘ + finalResult); }) .catch(failureCallback);
1、一定要有返回值,否则,callback将无法获取上一个Promise的结果。<br />2、也可以用箭头函数来表示<br />(1)如果使用箭头函数,() => x 比 () => { return x }更简洁一些,但后一种保留return的写法才支持使用多个语句。```javascript
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);
|
| —- |
组合:Promise.all()、Promise.race()
一、Promise.resolve()和Promise.reject()是手动创建一个已经resolve或者reject的Promise快捷方法。
二、Promise.all()和Promise.rece()是并行运行异步操作的两个组合式工具。
| 【示例】发起并行操作,然后等多个操作全部结束后进行下一步操作```javascript Promise.all([func1(), func2(), func3()]) .then(([result1, result2, result3])) => { / use result1, result2 and result3 / })
|
| --- |
| 【示例】可以使用一些聪明的JavaScript写法实现时序组合```javascript
[func1, func2, func3].reduce((p, f) => p.then(f), Promise.resolve())
.then(result3 => { /* use result3 */ })
| | —- |
| 【示例】递归调用由异步函数组成的数组时,相当于一个Promise链```javascript Promise.resolve().then(func1).then(func2).then(func3)
|
| --- |
| 【示例】可以写成可复用的函数形式,这在函数式编程中极为普遍```javascript
const applyAsync = (acc, val) => acc.then(val)
const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x))
1、composeAsync()函数将会接受任意数量的函数作为其参数,并返回一个新的函数,该函数接受一个通过composition pipeline传入的初始值。
(1)任一函数可以是异步或同步的,composeAsync()函数能保证它们按顺序执行。```javascript
const transformData = composeAsync(func1, func2, func3)
const result3 = transformData(data)
|
| --- |
| 【示例】在ECMAScript2017标准中,时序组合可以通过使用async/await而变得更简单```javascript
let result
for (const f of [func1, func2, func3]) {
result = await f(result)
}
/* use last result (i.e. result3) */
| | —- |
时序
一、为了避免意外,即使是一个已经变成resolve状态的Promise,传递给then()的函数也总是会被异步调用
| 【示例】```javascript Promise.resolve().then(() => console.log(2)) console.log(1)
// 1, 2
|
| --- |
二、传递到then()中的函数被置入到一个微任务队列中,而不会立即执行,这意味着它是在JavaScript事件队列的所有运行时结束了,且事件队列被清空之后,才开始执行
| 【示例】```javascript
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))
wait().then(() => console.log(4))
Promise.resolve().then(() => console.log(2)).then(() => console.log(3))
console.log(1)
// 1, 2, 3, 4
| | —- |