【JS】881- 我终于弄懂了Promise
转自:掘金 - 前端布吉岛
https://juejin.cn/post/6921593620680802311
写在前面
以前总是似懂非懂,这次总算把它弄了个清楚
什么是Promise
ES6 异步编程的一种解决方案,比传统的方案(回调函数和事件)更加的合理和强大 好处 异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数 promise可以解决异步的问题,本身不能说promise是异步的
Promise特点
对象的状态不受外界影响。 Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、resolved
(已成功)和rejected
(已失败)一旦状态改变,就不会再变,任何时候都可以得到这个结果。 Promise
对象的状态改变,只有两种可能:从pending
变为resolved
和从pending
变为rejected
promise内部发生错误,不会影响到外部程序的执行。 无法取消 Promise
。一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise
内部抛出的错误,不会反应到外部。第三,当处于pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
用法
基础用法
创造Promise
实例时,必须传入一个函数作为参数
new Promise(() => {});
new Promise(); // 报错
该函数可以接收另外两个由JavaScript引擎提供的函数,resolve
和reject
。函数作用:
resolve
——将Promise
对象的状态从pending
变为resolved
,将异步操作的结果,作为参数传递出去reject
——将Promise
对象的状态从pending
变为rejected
,将异步操作报出的错误,作为参数传递出去
let promise = new Promise((resolve, reject) => {
// do something
if (true) {
// 将参数返回,供then方法使用
resolve("value");
} else {
// 将参数返回,供then方法使用
reject("error");
}
});
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
promise.then(
value => {
// resolved时调用,value为resolve函数返回的参数
console.log(value);
},
err => {
// rejected时调用,err为reject函数返回的参数
console.log(err);
}
);
当then
方法只有一个函数参数时,此时为resolved
状态的回调方法
promise.then(value => {
// 只有状态为resolved时才能调用,如果返回的是rejected状态,则报错 Uncaught (in promise) error
console.log(value);
});
只有当promise的状态变为resolved或者rejected时,then方法才会被调用
Promise 新建后就会立即执行,并且调用resolve
或reject
后不会终结 Promise
的参数函数的执行。
let promise = new Promise(function(resolve) {
console.log("Promise");
resolve();
console.log("!!!")
});
promise.then(function() {
console.log("resolved.");
});
console.log("Hi!");
// Promise
// !!!
// Hi!
// resolved
resolve
返回的是另外一个Promise
实例
const p1 = new Promise((_, reject) => {
setTimeout(() => reject('error'), 3000);
});
const p2 = new Promise(resolve => {
setTimeout(() => resolve(p1), 1000);
});
p2.then(
result => console.log(result),
error => console.log(error) // error
);
上面代码中,
p1
是一个Promise
,3 秒之后变为rejected
。p2
的状态在 1 秒之后改变,resolve
方法返回的是p1
。由于p2
返回的是另一个 Promise,导致p2
自己的状态无效了,由p1
的状态决定p2
的状态。所以,后面的then
语句都变成针对后者(p1
)。又过了 2 秒,p1
变为rejected
,导致触发catch
方法指定的回调函数。
以上是原文解释,我们可以理解成p2.then
实际上是p1.then
Promise.prototype.then()
then
方法是定义在原型对象Promise.prototype
上的,同时then方法的两个参数都是非必选的。因为then方法返回的是一个**全新**
的promise实例时,因此then方法可以链式调用 then
方法在以下情况返回的promise
当未传入参数时,then方法会返回一个新的,状态和原promise相同的promise
const promise = new Promise(resolve => {
resolve("resolve");
});
let p = promise.then();
console.log(promise);
console.log(p);
结果展示
上一个promise未被成功调用then方法时,返回的结果如情形1
const promise = new Promise((_, reject) => {
reject("reject");
});
let a = promise.then(value => {
console.log(value);
});
上一个promise被成功调用then方法时,返回一个`resolve(undefined)`的promise
const promise = new Promise((_, reject) => {
reject("reject");
});
let a = promise.then(undefined, value => {
console.log(value);
});
console.log(a);
上一个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);
结果展示:
给then方法手动返回一个promise,此时会覆盖掉默认的行为,返回值为新增的promise
const promise = new Promise(resolve => {
resolve("resolve");
});
let p = promise.then(
() =>
new Promise(resolve => {
resolve("resolve2");
})
);
console.log(p);
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方法
Promise.prototype.finally()
finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。finally
方法的回调函数不接受任何参数,这表明,finally
方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
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;
}
);
finally
的返回值为一个新的和原来的值相似的promise
Promise.resolve()
有时需要将现有对象转为 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()
方法的对象,或根本就不是对象
const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s) // Hello
});
不带有任何参数
Promise.resolve()
方法允许调用时不带参数,直接返回一个resolved
状态的 Promise 对象
Promise.resolve();
// 相当于
new Promise(resolve => resolve(undefined))
Promise.reject()
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.all([p1, p2, p3]);
面代码中,Promise.all()
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise 实例,如果不是,就会调用Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()
方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。p
的状态由p1
、p2
、p3
决定,分成两种情况。
只有 p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。只要 p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
let promise = Promise.all([1, 2, 3]);
promise.then(value => {
console.log(value); // [1,2,3]
});
console.log(promise);
情形一promise结果:
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);
情形二promise结果:
如果作为参数的 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.race()
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。Promise.race()
方法的参数与Promise.all()
方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()
方法,将参数转为 Promise 实例,再进一步处理。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
Promise.allSettled()
Promise.allSettled()
方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是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
。fulfilled
时,对象有value
属性,rejected
时有reason
属性,对应两种状态的返回值。
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.try()
实际开发中,经常遇到一种情况:不知道或者不想区分,函数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
带你写出符合 Promise/A+ 规范 Promise 的源码
回复“加群”与大佬们一起交流学习~
点击“阅读原文”查看 100+ 篇原创文章