1.预备知识
1.1 实例对象与函数对象
- 实例对象:new 函数产生的对象,称为实例对象,简称为对象
- 函数对象:将函数作为对象使用时,称为函数对象
function Fn() { // Fn只能称为函数
}
const fn = new Fn() // Fn只有new过的才可以称为构造函数
//fn称为实例对象
console.log(Fn.prototype)// Fn作为对象使用时,才可以称为函数对象
Fn.bind({}) //Fn作为函数对象使用
$('#test') // $作为函数使用
$.get('/test') // $作为函数对象使用
- ()左边是函数,点左边是对象(函数对象、实例对象)
1.2 2种回调函数
-
-
同步回调
立即执行,完全执行完了才结束,不会放入回调队列中
如:数组遍历相关的回调 / [Promise])的executor函数
-
const arr = [1, 3, 5];
arr.forEach(item => {console.log(item); }) // // 遍历回调,同步回调,不会放入队列,一上来就要执行
console.log('forEach()之后')
-
2.异步回调
不会立即执行,会放入回调队列中将来执行
如:定时器回调 / ajax回调 / Promise成功或失败的.then 异步的回调
```js
定时器回调 setTimeout(() => { console.log('timeout callback()') }, 0) //// 异步回调,会放入队列中将来执行
console.log('setTimeout()之后')
// Promise 成功或失败的回调
new Promise((resolve, reject) => {
resolve(1)
}).then(
value => {console.log('value', value)},
reason => {console.log('reason', reason)}
)
console.log('----')
// ----
// value 1
.then 是异步的,
1.3 Promise是什么?
- Promise 是一门新的技术(ES6 规范)
- Promise 是 JS 中
进行异步编程的新解决方案 备注:旧方案是单纯使用回调函数 - 从语法上来说:
Promise是一个构造函数 (自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法) - 从功能上来说: promise 对象用来封装一个异步操作并可以获取其成功/ 失败的结果值
2. promise 的状态
- 实例对象promise中的一个属性
PromiseState
- pending 未决定的
resolved/fullfilled成功- rejected 失败
pending变为resolved/fullfilledpending变为rejected- 注意:状态的改变只有2种,-且一个
promise对象只能改变一次 - 一旦状态改变,就不会再变
- 无论成功还是失败,都会有一个结果数据。成功的结果数据一般称为
value,而失败的一般称为reason
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('失败')
resolve('成功')
}, 3000);
})
promise.then(res => console.log(res)).catch(err => console.log(err)
当我调用`reject`之后,在调用`resolve`是无效的,因为状态已经发生改变,并且是不可逆的。
- Promise对象的值
- 实例对象promise的另一个值
PromiseResult - 保存着对象 成功/失败 的值(
value/reason)
2.1 resolve不同值的区别
- 如果
resolve传入一个普通的值或者对象,只能传递接受一个参数,那么这个值会作为then回调的参数
const promise = new Promise((resolve, reject) => {
resolve({name: 'ice', age: 22})
})
promise.then(res => console.log(res))
- 如果
resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态
const promise = new Promise((resolve, reject) => {
resolve(new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ice')
}, 3000);
}))
})
promise.then(res => console.log(res))
//3s后 ice
3. Promise 的基本流程
4.为什么要用 Promise
5.Promise中的常用 API 概述
一 Promise 构造函数: Promise (excutor) {}
- executor 函数: 执行器 (resolve, reject) => {}
- resolve 函数: 内部定义成功时我们调用的函数 value => {}
- reject 函数: 内部定义失败时我们调用的函数 reason => {}
- 说明: executor 会在 Promise 内部立即
同步调用, - 异步操作
resolve/reject就在executor中执行
new Promise((resolve, reject) => { console.log(`executor 立即执行`) })
传入的`executor`是立即执行的
打印'executor 立即执行'
executor 指的是(resolve, reject) => { console.log(`executor 立即执行`) }
5.1 Promise的实例方法
- 实例方法,存放在
Promise.prototype上的方法,也就是Promise的显示原型上,当我new Promise的时候,会把返回的改对象的 promise[[prototype]](隐式原型) === Promise.prototype (显示原型) - 即new返回的对象的隐式原型指向了Promise的显示原型
5.2 Promise.resolve 方法:Promise.resolve(value)
value:将被 Promise 对象解析的参数,也可以是一个成功或失败的 Promise 对象
说明:返回一个成功或失败的promise对象
(1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
let p2 = Promise.resolve(new Promise((resolve, reject) => {
// resolve('OK'); // 成功的Promise
reject('Error');
}));
console.log(p2);
p2.catch(reason => {
console.log(reason);
})
此参数为失败的promise对象,返回的也是失败的promise对象,则参数的结果决定了 resolve 的结果
(2)参数是一个thenable对象
thenable对象指的是具有then方法的对象,比如下面这个对象。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法
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。
(3)参数不是具有then()方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。
const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s)
});
// Hello
(4)不带有任何参数
Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。
const p = Promise.resolve();
p.then(function () {
// ...
});
上面代码的变量`p`就是一个 Promise 对象。
5.3 Promise.reject 方法
说明:返回一个失败的 promise 对象
let p = Promise.reject(521);
let p2 = Promise.reject('iloveyou');
let p3 = Promise.reject(new Promise((resolve, reject) => {
resolve('OK');
}));
console.log(p);
console.log(p2);
console.log(p3);
更多例子:
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
上面代码生成一个 Promise 对象的实例`p`,状态为`rejected`,回调函数会立即执行。
Promise.reject('出错了')
.catch(e => {
console.log(e === '出错了')
})
// true
上面代码中,`Promise.reject()`方法的参数是一个字符串,后面`catch()`方法的参数`e`就是这个字符串。
- Promise.resolve() / Promise.reject() 方法就是一个语法糖
- 用来快速得到Promise对象
5.4 then方法
then方法可以接受参数,一个参数为成功的回调,另一个参数为失败的回调
const promise = new Promise((resolve, reject) => {
resolve('request success')
// reject('request error')
})
promise.then(res => console.log(res), rej => console.log(rej))
//request success
- 如果只捕获错误,还可以这样写:- 因为第二个参数是捕获异常的,第一个可以写个
null或""占位
const promise = new Promise((resolve, reject) => {
// resolve('request success')
reject('request error')
})
promise.then(null, rej => console.log(rej))
//request error
- then的多次调用
const promise = new Promise((resolve, reject) => {
resolve('hi ice')
})
promise.then(res => console.log(res))
promise.then(res => console.log(res))
promise.then(res => console.log(res))
-
then的返回值
then方法是有返回值的,它的返回值是promise
5.5 Promise.all 方法:Promise.all(iterable)
iterable:包含 n 个 promise 的可迭代对象,如 Array 或 String
说明:返回一个新的 promise,只有所有的 promise 都成功才成功,只要有一个失败了就直接失败
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
const result = Promise.all([p1, p2, p3]);
console.log(result);
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.reject('Error');
let p3 = Promise.resolve('Oh Yeah');
const result = Promise.all([p1, p2, p3]);
console.log(result);
const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)
const pAll = Promise.all([p1, p2, p3])
const pAll2 = Promise.all([p1, p2])
//因为其中p3是失败所以pAll失败
pAll.then(
value => {
console.log('all onResolved()', value)
},
reason => {
console.log('all onRejected()', reason)
}
)
// all onRejected() 3
pAll2.then(
values => {
console.log('all onResolved()', values)
},
reason => {
console.log('all onRejected()', reason)
}
)
// all onResolved() [1, 2]
- 当有一个失败,.then返回的是失败的值,
- 当所有promise成功,.then 返回的值,是数组,数组里面所有成功的值
Promise.race方法
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
all是等所有的异步操作都执行完了再执行then方法,那么race方法就是相反的,谁先执行完成就先执行回调。先执行完的不管是进行了race的成功回调还是失败回调,其余的将不会再进入race的任何回调
function promiseClick1(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('2s随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('2s数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
function promiseClick2(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('3s随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('3s数字太于10了即将执行失败回调');
}
}, 3000);
})
return p
}
function promiseClick3(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('4s随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('4s数字太于10了即将执行失败回调');
}
}, 4000);
})
return p
}
Promise
.race([promiseClick3(), promiseClick2(), promiseClick1()])
.then(function(results){
console.log('成功',results);
},function(reason){
console.log('失败',reason);
});
当2s后promiseClick1执行完成后就已经进入到了then里面回调,在then里面的回调开始执行时,promiseClick2()和promiseClick3()并没有停止,仍旧再执行。于是再过3秒后,输出了他们各自的值,但是将不会再进入race的任何回调。**如图2s生成10进入race的成功回调后,其余函数继续执行,但是将不会再进入race的任何回调,2s生成16进入了race的失败回调,其余的继续执行,但是将不会再进入race的任何回调。
race的使用比如可以使用在一个请求在10s内请求成功的话就走then方法,如果10s内没有请求成功的话进入reject回调执行另一个操作。
//请求某个table数据
function requestTableList(){
var p = new Promise((resolve, reject) => {
//去后台请求数据,这里可以是ajax,可以是axios,可以是fetch
resolve(res);
});
return p;
}
//延时函数,用于给请求计时 10s
function timeout(){
var p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时');
}, 10000);
});
return p;
}
Promise.race([requestTableList(), timeout()]).then((data) =>{
//进行成功回调处理
console.log(data);
}).catch((err) => {
// 失败回调处理
console.log(err);
});
请求一个接口数据,10s内请求完成就展示数据,10s内没有请求完成就提示请求失败
这里定义了两个promise,一个去请求数据,一个记时10s,把两个promise丢进race里面赛跑去,如果请求数据先跑完就直接进入.then成功回调,将请求回来的数据进行展示;如果计时先跑完,也就是10s了数据请求还没有成功,就先进入race的失败回调,就提示用户数据请求失败进入.catch回调,(ps:或者进入reject的失败回调,当.then里面没有写reject回调的时候失败回调会直接进入.catch)
几个关键的问题
1
const p =new Promise(( resolve,reject)=>{
throw new Error('出错了')
// 这里的代码出错误了或主动抛出异常,promise变为rejected失败状态,
})
p.then(
value=>{},
reason=>{console.log('reason',reason);}
)
p.then(
value=>{},
reason=>{console.log('reason',reason);}
)
// 多次调用,都会执行回调函数
2
3
4
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(1)
console.log('异步任务1');
},1000)
}).then(
value=>{
console.log('任务1的结果',value);
console.log('同步任务2');
return 2
},
).then((value)=>{
console.log( '任务2的结果',value);
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(3)
console.log('异步任务3');
},1000)
})
}).then(
value=>{console.log('任务3的结果',value);}
)
5
new Promise((resolve,reject)=>{
reject(1)
}).then(
value=>{
console.log( 'onResolved1()',value);
return 2
}
).then(
value=>{
console.log('onResolved2()',value);
return 3
}
).catch(
reason=>{
console.log('onRjected()',reason); //onRjected() 1
return new Promise(()=>{})
// 返回一个pedding状态的Promise,中断,后面的then没有输出
}
).then(
value=>{ console.log( 'onResolve3',value);},
reason=>{ console.log( 'onReject3',reason);},
)
async与await
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// "hello world"
上面代码中,函数`f`内部`return`命令返回的值,会被`then`方法回调函数接收到。
async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。法回调函数接收到。抛出的错误对象会被catch方法回调函数接收到
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log('resolve', v),
e => console.log('reject', e)
)
//reject Error: 出错了
promise对象的状态变化
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)</title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
上面代码中,函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行then方法里面的console.log。
await
任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
上面代码中,第二个`await`语句是不会执行的,因为第一个`await`语句状态变成了`reject`。
错误处理
如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject。
async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错
上面代码中,`async`函数`f`执行后,`await`后面的 Promise 对象会抛出一个错误对象,导致`catch`方法的回调函数被调用,它的参数就是抛出的错误对象。
防止出错的方法,也是将其放在try...catch代码块之中。
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
} catch(e) {
console.log(e);
}
return await('hello world'); //当上面的代码出错误,执行这个代码
}
f().then()
如果有多个await命令,可以统一放在try...catch结构中。
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
注意点
第一点,前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}
第二点,多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发
let foo = await getFoo();
let bar = await getBar();
上面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
上面两种写法,`getFoo`和`getBar`都是同时触发,这样就会缩短程序的执行时间。
第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}
js异步操作之宏队列与微队列
面试题目