转载请注明预见才能遇见的博客:https://blog.csdn.net/pcaxb
原文地址:https://blog.csdn.net/pcaxb/article/details/102587763
目录
JavaScriptES6系列-Promise 对象详解
1.Promise 的含义
2.基本用法
3.Promise.prototype.then()
4.Promise.prototype.catch()
5.Promise.prototype.finally()
6.Promise.all()
7.Promise.race()
8.Promise.allSettled() ES2020引入
9.Promise.any() 提案
10.Promise.resolve()
(1)参数是一个 Promise 实例
(2)参数是一个thenable对象
(3)参数不是具有then方法的对象,或根本就不是对象
(4)不带有任何参数
11.Promise.reject()
12.应用
(1)加载图片
(2)Generator 函数与 Promise 的结合
13.Promise.try() 提案
1.Promise 的含义
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。Promise在ES6之前就有,只是ES6 将其写进了语言标准,统一了用法。Promise简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise
无法中途取消,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise
对象有以下两个特点:
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
注意:本章后面的resolved
统一只指fulfilled
状态,不包含rejected
状态。
2.基本用法
const promise = new Promise(function(resolve,reject) {
let async = true;//异步操作成功
if(async){
resolve("成功");
console.log("调用resolve或reject并不会终结 Promise 的参数函数的执行");//打印
}else{
reject("失败");
}
});
//第一个参数是成功回调,第二个参数是失败回调(可选)
promise.then(function(res) {
log(res);
},function(error) {
log(error);
});
//推荐使用catche,不推荐使用then的第二个参数作为失败的回调
promise.then(function(res) {//成功
}).catch(function(error) {//失败
console.log("error="+error)
});
一个函数作为Promise的参数,函数的参数是两个函数resolve和reject,所以名字当然也是随便取哦。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
//1
function timeout(ms) {
return new Promise((resolve, reject) => {
//setTimeout第三个参数是第一个参数函数的参数
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
//2
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
//then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// 'Promise' 然后 'Hi!' 然后 'resolved.'
//3
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 40000)
})
p2//这里的result不是p1而是p1的结果
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
一般来说,调用resolve
或reject
以后,Promise 的使命就完成了,后继操作应该放到then
方法里面,而不应该直接写在resolve
或reject
的后面。所以,最好在它们前面加上return
语句,这样就不会有意外。
3.Promise.prototype.then()
then
方法是定义在原型对象Promise.prototype
上的,它的作用是为 Promise 实例添加状态改变时的回调函数。then
方法的第一个参数是resolved
状态的回调函数,第二个参数(可选)是rejected
状态的回调函数。
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject("11111111"), 1000)
});
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => reject("22222222"), 1000)
});
p1.then(function(res) {
console.log("success");
return res;
//使用return可以给下一个then传值
},function(error) {
console.log("fail");
return error;
}).then(function(result) {
console.log(result);
return p2;
//如果return返回的是promise,下一个then就会有成功和失败的接收
}).then(function(res) {
console.log("success");
console.log(res);
},function(error) {
console.log("fail");
console.log(error);
})
then
方法返回的是一个新的Promise
实例,因此可以采用链式写法,即then
方法后面再调用另一个then
方法。第一个回调函数完成以后,使用return会将返回结果作为参数,传入第二个回调函数。
4.Promise.prototype.catch()
Promise.prototype.catch
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
//1
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject("11111111"), 1000)
});
p1.catch(function(error) {
console.log(error);//11111111
})
//2
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => resolve("11111111"), 1000)
});
p1.then(function(res) {
console.log("成功");
console.log(a);//错误
},function(params) {
console.log(params+"===");
console.log(a);//错误
})
.catch(function(params) {
console.log(params + "===")//成功和失败回调函数有错误会调用
})
//3
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject("11111111"), 1000)
});
p1.then(function(res) {
console.log("成功");
},function(params) {
console.log(params+"===");
return "kkkkkkk"
})
.catch(function(params) {
console.log(params + "===")//错误时候使用return,不会调用
})
//4
p1.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p1.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
//5
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
如果异步操作抛出错误,或者then
方法指定的回调函数在运行中抛出错误会被catch
方法捕获。或者使用reject
方法,等同于抛出错误。
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch
语句捕获。
const p1 = new Promise(function (resolve, reject) {
reject("111")
});
p1.then(function(res) {
}).then(function(res) {
},function(error) {
//如果没有这个回调函数就会执行后面的catch回调函数
console.log("fail="+error)
}).catch(function(error) {
console.log("error="+error)
});
Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”
const p1 = new Promise(function (resolve, reject) {
console.log(a);//报错
});
setTimeout(function(params) {
console.log(1111);//Promise报错,程序不会停止,后面的依然会执行
},2000)
catch
方法返回的还是一个 Promise 对象,因此后面还可以接着调用then
方法。如果没有报错,则会跳过catch方法。
const p1 = new Promise(function (resolve, reject) {
resolve("1");
});
p1.then(function(res) {
console.log("1");
}).then(function(res) {
console.log("2");
}).catch(function(error) {
console.log("3");
}).then(function(res) {
console.log("4");
});//1 2 4 如果没有报错,则会跳过catch方法
//说明 then 是可以一级一级传递的,不管有没有return给下一个then传参数
catch
方法也可以抛出一个错误,如果后面没有别的catch
方法了,就会导致这个错误不会被捕获,也不会传递到外层。
5.Promise.prototype.finally()
finally
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。finally
方法的回调函数不接受任何参数。
const p1 = new Promise(function (resolve, reject) {
resolve("1");
});
p1.finally(function() {
//回调函数没有参数,任何状态都会执行
})
finally的实现
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
6.Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。参数是promise数组或者是promise类数组
//创建Promise工厂
function createPromise(id) {
return new Promise(function(resolve,reject) {
setTimeout(resolve,1000,id)
})
}
//通过map把一个数组生成一个新的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return createPromise(id);
});
var p = Promise.all(promises);
p.then(function (res) {
//promises中的所有promise的状态都变成fulfilled,p的状态才会变成fulfilled,
//此时promises中的所有promise的返回结果组成一个数组,传递给p的resolve回调函数
console.log(res);//[2, 3, 5, 7, 11, 13]
}).catch(function(error){
//只要promises中有一个promise的状态变成rejected,P的状态就会变成rejected,
//此时,第一个被rejected的返回结果就会唱给p的reject回调函数
});
注意,如果作为参数的 Promise 实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
上面代码中,p1
会resolved
,p2
首先会rejected
,但是p2
有自己的catch
方法,该方法返回的是一个新的 Promise 实例,p2
指向的实际上是这个实例。该实例执行完catch
方法后,也会变成resolved
,导致Promise.all()
方法参数里面的两个实例都会resolved
,因此会调用then
方法指定的回调函数,而不会调用catch
方法指定的回调函数。如果p2
没有自己的catch
方法,就会调用Promise.all()
的catch
方法。
7.Promise.race()
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p.then(console.log)
.catch(console.error);
上面代码中,如果 5 秒之内fetch
方法无法返回结果,变量p
的状态就会变为rejected
,从而触发catch
方法指定的回调函数。
8.Promise.allSettled() ES2020引入
Promise.allSettled()
方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled
还是rejected
,包装实例才会结束。
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
上面代码中,Promise.allSettled()
的返回值allSettledPromise
,状态只可能变成fulfilled
。它的监听函数接收到的参数是数组results
。该数组的每个成员都是一个对象,对应传入Promise.allSettled()
的两个 Promise 实例。每个对象都有status
属性,该属性的值只可能是字符串fulfilled
或字符串rejected
。fulfilled
时,对象有value
属性,rejected
时有reason
属性,对应两种状态的返回值。
有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()
方法就很有用。
9.Promise.any() 提案
Promise.any()
方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.any([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
allSettledPromise.catch(function (results) {
console.log(results);
});
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected
状态而结束。
10.Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve()
方法就起到这个作用。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
如果参数是 Promise 实例,那么Promise.resolve
将不做任何修改、原封不动地返回这个实例。
thenable
对象thenable
对象指的是具有then
方法的对象,Promise.resolve
方法会将这个对象转为 Promise 对象,然后就立即执行thenable
对象的then
方法。
let thenable = {//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
方法的对象,或根本就不是对象如果参数是一个原始值,或者是一个不具有then
方法的对象,则Promise.resolve
方法返回一个新的 Promise 对象,状态为resolved
。
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hell
Promise.resolve
方法的参数,会同时传给回调函数。
需要注意的是,立即resolve()
的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
etTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
上面代码中,setTimeout(fn, 0)
在下一轮“事件循环”开始时执行,Promise.resolve()
在本轮“事件循环”结束时执行,console.log('one')
则是立即执行,因此最先输出。
11.Promise.reject()
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
注意,Promise.reject()
方法的参数,会原封不动地作为reject
的理由,变成后续方法的参数。这一点与Promise.resolve
方法不一致。
12.应用
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
const g = function* () {
try {
const foo = yield getFoo();
console.log(foo);
} catch (e) {
console.log(e);
}
};
function run (generator) {
const it = generator();
function go(result) {
if (result.done) return result.value;
return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}
go(it.next());
}
run(g);
13.Promise.try() 提案
实际开发中,经常遇到一种情况:不知道或者不想区分,函数f
是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f
是否包含异步操作,都用then
方法指定下一步流程,用catch
方法处理f
抛出的错误。一般就会采用下面的写法。
Promise.resolve().then(f)
上面的写法有一个缺点,就是如果f
是同步函数,那么它会在本轮事件循环的末尾执行。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
上面代码中,函数f
是同步的,但是用 Promise 包装了以后,就变成异步执行了。那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是可以的,并且还有两种写法。
第一种写法是用async
函数来写。
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async
函数,因此如果f
是同步的,就会得到同步的结果;如果f
是异步的,就可以用then
指定下一步,就像下面的写法。
(async () => f())()
.then(...)
需要注意的是,async () => f()
会吃掉f()
抛出的错误。所以,如果想捕获错误,要使用promise.catch
方法。
(async () => f())()
.then(...)
.catch(...)
第二种写法是使用new Promise()
。
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
提供Promise.try
方法替代上面的写法。
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
由于Promise.try
为所有操作提供了统一的处理机制,所以如果想用then
方法管理流程,最好都用Promise.try
包装一下。这样有许多好处,其中一点就是可以更好地管理异常。
function getUsername(userId) {
return database.users.get({id: userId})
.then(function(user) {
return user.name;
});
}
上面代码中,database.users.get()
返回一个 Promise 对象,如果抛出异步错误,可以用catch
方法捕获,就像下面这样写。
database.users.get({id: userId})
.then(...)
.catch(...)
但是database.users.get()
可能还会抛出同步错误(比如数据库连接错误,具体要看实现方法),这时你就不得不用try...catch
去捕获。
try {
database.users.get({id: userId})
.then(...)
.catch(...)
} catch (e) {
// ...
}
上面这样的写法就很笨拙了,这时就可以统一用promise.catch()
捕获所有同步和异步的错误。
Promise.try(() => database.users.get({id: userId}))
.then(...)
.catch(...)
事实上,Promise.try
就是模拟try
代码块,就像promise.catch
模拟的是catch
代码块。
JavaScriptES6系列-Promise 对象详解
博客地址:https://blog.csdn.net/pcaxb/article/details/102587763