手写promise
//构建了三个常量用于表示状态,对于经常使用的一些值都应该通过常量来管理,便于开发和后期维护
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
// 在函数内部首先创建了常量that,因为函数可能会异步执行,用于获取正确的this对象
const that = this;
// 一开始Promise的状态应该是pending
that.state = PENDING;
// value变量用于保存resolve或者rekect中传入的值
that.value = null;
// resolvedCallbacks和rejectCallbacks用于保存then中的回调,因为当执行完Promise的状态可能还在等待中,这时候应该把then中的回调保存起来用于状态改变时使用
that.resolvedCallbacks = [];
that.rejectedCallbacks = [];
// 完善resolve和reject函数
function resolve(value) {
if (that.state === PENDING) {
that.state = RESOLVED;
that.value = value;
that.resolvedCallbacks.map((cb) => cb(that.value));
}
}
function reject(value) {
if (that.state === PENDING) {
that.state = REJECTED;
that.value = value;
that.rejectedCallbacks.map((cb) => cb(that.value));
}
}
// 执行传入的参数并且将之前两个函数当作参数传进去
// 可能执行函数过程中会遇到错误,需要捕获出错文件并且执行reject函数
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
const that = this;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === 'function'
? onRejected
: (r) => {
throw r;
};
if (that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled);
that.rejectedCallbacks.push(onRejected);
}
if (that.state === RESOLVED) {
onFulfilled(that.value);
}
if (that.state === REJECTED) {
onRejected(that.value);
}
};
function create(Con,...args) {
// 首先函数接受不确定数量的参数,第一个参数为构造函数,其他参数为构造函数所用
// 内部创建一个空对象
let obj = {};
// 因为obj需要访问到构造函数原型链上的属性,所以通过原型量将两者联系起来
// 下面这两句的效果是一样的
// obj.__proto__ = Con.prototype;
Object.setPrototypeOf(obj,Con.prototype);
// 将this指向obj对象
let result = Con.call(obj, ...args);
// 判断构造函数返回的是否是函数,如果不是,则返回obj,这样实现了,忽略构造函数返回原始值的操作
return result instanceof Object ? result : obj;
}
let promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject(2);
}, 2000);
}).then(
(res) => {
console.log(res);
},
(errmsg) => {
console.log(errmsg);
}
);
promise的方法
Promise.prototype.fianlly()
- 不管promise最后的状态是什么,都会执行的操作
- 回调函数不接受参数,所以就没有办法知道前面promise的状态是resolved还是rejected
- finally是then的特殊状态
promise.finally(() => {
// 语句
});
// 等同于
promise.then(
result => {// 语句
return result;},
error => // 语句
throw error});
Promise.all()
const p = Promise.all([p1, p2, p3]);
-
用于将多个promise对象,包装成一个promise对象的方法,接受一个数组作为一个参数 如果参数不是promise对象,则会先调用resolve方法,将参数转化为promise实例, 参数可以不是数组,但是必须有Interator接口,且返回的实例必须为promise对象
-
p的状态由p1,p2,p3的状态决定的
- 只有p1,p2,p3的状态都变为fulfilled,p的状态才会变为fulfilled,并且将p1,p2,p3的返回值组成一个数组,传递给p的回调函数
- 只要p1,p2,p3的状态有一个为rejected,则p的值为rejected,第一个被rejected的值传递给p的回调函数
-
所以当比如有6个promise的时候,只有这6个都变为fulfilled,或者其中一个变为rejected,才会调用到all后面的方法
-
⚠️:如果作为参数的promise对象自己定义了promise对象的catch方法,那么,即使返回了catch,也不会进入all方法内,如果作为参数的promise没有catch方法,则会进入all的方法。
Promise.race
const p = Promise.race([p1, p2, p3]);
-
只要参数里面有一个参数的值的状态发生了变化,那么就会调用race里面的方法 率先改变的Promise实例的值会返回
-
例子:如果5秒内,fetch没有返回值,则会返回rejected,从而出发catch里面的函数
Promise.allSettled()
-
接受一组promise实例作为参数,包装成一个新的promise实例,是有当这些参数全部返回结果,不管是fulfilled还是rejected,包装实例才会结束
-
结束后,返回一个新的promise实例,实例的状态总是fulfilled,不会是rejected,promise实例接收到的参数是一个数组
用法的例子:
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === 'fulfilled');
// 过滤出失败的请求,并输出原因
const errors = results
.filter(p => p.status === 'rejected')
.map(p => p.reason);
Promise.any
-
接受一组promise实例作为参数,这组参数有一个变为fulfilled,则状态变为fulfilled,只有所有的状态均变为rejected,结果状态才会变为rejected状态
-
与Promise.race的区别: Promise.race,不会由于某个promise的状态变为rejected而结束
Promise.resolve()
-
有时需要将现有对象转化为promise对象,resolve就是起这个作用 const jsPromise = Promise.resolve($.ajax('/whatever.json')); 以上代码就是将ajax形成的deferred对象转化为promise对象
-
参数分为4种情况
-
参数是一个promise实例,不做任何处理,原封不动的返回
-
参数是一个thenable对象
// thenable对象:具有then方法的对象
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
会将对象转化为promise对象,然后立即执行thenable对象的then方法
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
- 参数不是具有then方法的实例,或者根本不是对象,如果参数是一个初始值或者不具有then方法的对象,则返回一个promise对象,状态为resolve状态
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
// 会将resolve的参数作为参数传给then方法
- 不带任何参数 直接返回一个resolved状态的promise对象 所以,如果希望得到一个promise对象,比较方便的方法是可以直接调用promise.resolve()
⚠️:立即resolve的对象,会在本次事件轮询结束的时候执行,而不是下一次事件轮询开始的时候
// 例如:
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
Promise.reject()
方法会返回一个promise对象,对象的状态为rejected
promise.rejected()的参数会直接作为rejected的理由,这个与resolve方法不同
例如:
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
// promise对象的参数不是传进来的“出错了”字符串,而是thenable方法
Promise.try()
- 要解决的问题 不知道或者不清楚某一个函数是否含有异步操作,想用promise进行处理,这样,不管是否含有异步操作,都可以用then指定下一步流程,用catch来处理错误
如果使用Promise.resolve()引起的问题
// 缺点:同步操作会在本次事件轮训的最后执行
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
解决方法:同步的同步处理,异步的异步处理 方法一:用async处理
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
如果f是异步的,可以使用then来处理后面的操作 (async () => f())() .then(...)
需要注意的是:这种操作会吃掉错误,所以要想要捕获错误,要使用catch()进行捕获
方法二:使用自执行函数
方法二:new Promise()
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
- 提出try的好处
更好的错误处理
同步代码的异常,无论在何处出现,都会以rejection的方式向promise链后段传递
function getUsername(userId) { return database.users.get({id: userId}) .then(function(user) { return uesr.name; }); } 这样写的话,uesr拼写错误,这行会出现异常,这个异常会被.then()捕获,并被转化为rejected promise 但是如果database.users.get同步的抛出了异常呢?如果在第三方的代码里存在拼写错误或是其他问题呢?Promises的错误捕获机制的前提是使用者编写的所有同步代码都要放在.then中,这样它就可以将这些同步代码放入一个try/catch块中 但是。。。我们代码中的database.users.get并不在.then块中。因此Promise就不能访问这部分代码也就不能将它们包裹在try/catch中。这就导致同步异常不能被转变为异步异常。我们又回到了原点,不得不处理两种形式的异常 —— 同步的和异步的
使用了promise.try()
var Promise = require("bluebird");
function getUsername(userId) { return Promise.try(function() { return database.users.get({id: userID}); }).then(function(user) { return user.name; }); } 我前边说过Promise.try很像.then,但是它不需要跟在Promise后边。除此之外,它还会捕获database.users.get中的同步异常,就像.then一样! 通过引入Promise.try,我们已经把我们代码的错误处理由只能捕获第一次异步操作(第一次.then调用)之后的异常改造为可以覆盖所有的同步异常。
更好的兼容性,可是使用自己喜欢的promise实现,而不用担心第三方在使用哪个
更好的代码阅读体验 都在同一个缩紧上,比较好阅读
Promise.all和Promise.allsettled的区别
Promise.all方法只有在接受的所有Promise实例的状态都是fulfilled才会走,只要有一个的状态是rejected就会走catch方法。
Promise.allsettled方法无论promise实例的状态是fulfilled还是rejected都会走。
Promise.race和Promise.any的区别
Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise ,关注是被解决还是被拒绝。
Promise.race()方法主要关注 Promise 是否已解决