Promise是什么:
1、认识Promise:
Promise 是异步操作的一种解决方案。
先给大家讲一下什么是异步:
回调函数其实就是异步操作,例:
document.addEventListener(
'click',
() => {
console.log('这里是异步的');
},
false
);
console.log('这里是同步的');
2、什么时候使用 Promise:
Promise 一般用来解决层层嵌套的回调函数(回调地狱 callback hell)的问题。
在这里插入代码片
Promise的基本用法:
1、实例化构造函数生成实例对象:
Promise 解决的不是回调函数,而是回调地狱,即并不是用了Promise就用不了回调函数,它们两个是可以共存的。
// 实例化构造函数生成实例对象:
const p = new Promise(() => {});
2、Promise 的状态:
Promise的状态解决了Promise的一系列行为。Promise 有 3 种状态,一开始是 pending(未完成),执行 resolve,变成 fulfilled(resolved),已成功;执行 reject,变成 rejected,已失败;Promise 的状态一旦变化,就不会再改变了。
一开始是 pending(未完成):
const p = new Promise((resolve, reject) => {
});
console.log(p); // Promise {<pending>
执行 resolve,变成 fulfilled(resolved),已成功:
const p = new Promise((resolve, reject) => {
resolve();
});
console.log(p); // Promise {<fulfilled>: undefined}
执行 reject,变成 rejected,已失败:
const p = new Promise((resolve, reject) => {
reject();
});
console.log(p); // Promise {<rejected>: undefined}
但这里请大家注意:Promise里面第一个参数是成功状态的回调函数,第二个参数是失败状态的回调函数,Promise里面的resolve和reject只是形参(名字可以随便写,想起什么名就起什么名),写成resolve和reject的原因是为了语义化更强一些,那么,请问下面代码执行结果是什么:
const p = new Promise((r1,r2) => {
r2();
});
console.log(p);
因为r1 和 r2 只是形参,第二个参数是失败状态的回调函数,所以调用 r2 就等同于调用 失败状态的回调函数 所以毫无疑问结果是Promise {<rejected>: undefined}
,在这里只是给大家提个醒,大家不要弄迷糊了。
Promise 的状态一旦变化,就不会再改变了:
const p = new Promise((resolve, reject) => {
resolve();
reject();
});
console.log(p); // Promise {<fulfilled>: undefined}
3、then方法(简单说明一下,具体在后面讲):
这里then传两个函数,成功了用第一个函数,失败了用第二个函数。
// 失败:
const p = new Promise((resolve, reject) => {
reject();
});
p.then(
() => {
console.log('success');
},
() => {
console.log('error');
}
);
// error
// 成功:
const p = new Promise((resolve, reject) => {
resolve();
});
p.then(
() => {
console.log('success');
},
() => {
console.log('error');
}
);
// success
4、resolve 和 reject 函数的参数:
resolve 和 reject 函数的参数都会传给相应的then方法里面函数的形参,即resolve()传它的参数给then里面第一个函数的形参,reject()传它的参数给then里面第二个函数的形参;举例:
// 成功的时候:
const p = new Promise((resolve, reject) => {
resolve({username:'Alex'});
});
p.then(
data => {
console.log('success', data);
},
err => {
console.log('error', err);
}
);
// success {username: "alex"}
// 失败的时候:
const p = new Promise((resolve, reject) => {
reject(new Error('reason'));
});
p.then(
data => {
console.log('success', data);
},
err => {
console.log('error', err);
}
);
// error Error: reason
// at 2-2.Promise 的基本用法.html:28
// at new Promise (<anonymous>)
// at 2-2.Promise 的基本用法.html:16
then方法:
1、什么时候执行:
pending->fulfilled 时,执行 then 的第一个回调函数
pending->rejected 时,执行 then 的第二个回调函数
2、执行后的返回值:
then 方法执行后返回一个新的 Promise 对象。
const p = new Promise((resolve, reject) => {
resolve();
// reject();
});
const p2 = p
.then(
() => {},
() => {}
)
.then() // 因为then方法返回的是一个promise,所以可以在后面再添加then方法。
.then();
console.log(p, p2, p === p2); // Promise {<fulfilled>: undefined} Promise {<pending>} false
3、then 方法返回的 Promise 对象的状态改变:
在前面讲过第一个new 的promise可以决定紧挨着后面then的执行回调函数,例:
const p = new Promise((resolve, reject) => {
reject();
});
p.then(
() => {
console.log('success');
},
() => {
console.log('err');
});
// err
因为在p中执行reject(),所以执行p紧挨着的then的第二个回调函数,因此输出err,
而我们都知道then方法可以继续使用在上一个then方法的后面的,但是大家有没有想过新的then方法是怎么执行的呢?下面进行讲解:
在讲解之前大家先判断一下下面这块代码的打印出来的值是多少?
// 案例1:
const p = new Promise((resolve, reject) => {
reject();
});
p.then(
() => {
console.log('success');
},
() => {
console.log('err');
}).then(
data => {
console.log('success2', data);
},
err => {
console.log('err2', err);
}
)
这个值是err success2 undefined,
我先给大家分析一下,在p中执行reject(),所以在第一个then中执行第二个回调函数,所以打印出err,然后在第一个then中默认返回的永远都是成功状态的 Promise 对象,所以在第二个then中调用第第一个回调函数,所以接着打印出success2 undefined,由此可见后面的then执行第一个回调函数还是第二个回调函数看的是前面的then,可能大家对
在第一个then中默认返回的永远都是成功状态的 Promise 对象
这句话不太理解 ,下面我通过代码给大家演示:
先简单通过代码看一下:
// 在 then 的回调函数中,return 后面的东西,会用 Promise 包装一下
return undefined;
// 等价于
return new Promise(resolve => {
resolve(undefined);
});
这是详细讲解:
1、在所有函数中都有return值,没有写的时候就默认返回undefined,所以在第一个then里面就默认返回undefined(相当于程序中默认有 return undefined;
这样一行代码)
() => {
console.log('err');
// 默认这里有 return undefined;这样一行代码
})
又因为 默认返回的永远都是成功状态的 Promise 对象,即:
() => {
console.log('err');
// 默认这里有 return undefined;这样一行代码
// 因为默认返回的永远都是成功状态的 Promise 对象,所以相当于一个新的Promise里面执行resolve(),即:
return new Promise((resolve, reject) => {
resolve(undefined); // 因为默认返回undefined,所以resolve里面参数是undefined
})
因为上一个then执行resolve,所以到第二个then那里就执行第一个回调函数,所以打印出 success2 undefined,打印出undefined是因为在一个then里面的reject传进去的undefined。讲到这里大家可能会问:默认返回的永远都是成功状态的 Promise 对象,那么如何返回失败状态的Promise对象呢?其实是这样的,then里面默认的是返回的永远都是成功状态的 Promise 对象,那么我直接给一个失败状态的Promise,那么不就不会再出现默认值了呢,没错,就是这样。举例:
const p = new Promise((resolve, reject) => {
reject();
});
p.then(
() => {
console.log('success');
},
() => {
console.log('err');
/
return new Promise((resolve, reject) => {
reject('reason');
});
/
}).then(
data => {
console.log('success2', data);
},
err => {
console.log('err2', err);
}
)
大家注意横线里面的两行代码,那样写即程序不会再执行那个 默认的正确状态的Promis对象了,而执行自己写的那个错误的Promise对象(即/中间的代码),所以,这个案例的执行结果是:err err2 reason,原理同案例1一样。
那么简单测试一下上面知识是否掌握了,上案例:
const p = new Promise((resolve, reject) => {
reject();
});
p.then(
() => {
console.log('success');
},
() => {
// 步骤一:
console.log('err');
}).then(
data => {
// 步骤二:
console.log('success2', data);
},
err => {
console.log('err2', err);
}
).then(
data => {
// 步骤三:
console.log('success3', data);
},
err => {
console.log('err3', err);
}
);
只要大家将上面知识理解了,那么这个案例就是小菜一碟,答案是:err success2 undefined success3 undefined,执行的分别是步骤一,步骤二,步骤三。
catch方法:
1、有什么用:
在前面的then方法中,我们知道它里面有两个回调函数(第一个处理成功的状态,第二个处理失败的状态),但在实际开发中一般then只处理成功的状态,因此只传一个回调函数,因此catch就是用来专门处理失败的状态的,即catch 本质上是 then 的特例,就是then(null, err => {});
2、基本用法:
new Promise((resolve, reject) => {
// resolve(123);
reject('reason');
}).then(data => {
console.log(data); // then 处理正确的状态
}).catch(err => {
console.log(err); // catch 处理错误的状态
})
// reason
// 上面的catch其实就相当于
.then(null, err => {
console.log(err);
});
catch() 可以捕获它前面的错误,一般总是建议,Promise 对象后面要跟 catch 方法,这样可以处理 Promise 内部发生的错误:
catch可以捕获前面的错误,如果错误一旦被catc捕获到则错误就消失了。还有就是catch默认也是返回一个成功状态的Promise对象(原因很简单,catch本质是then,因为then默认返回成功状态的Promise对象,所以catch也是返回成功状态的Promise对象),所以catch后面跟的then只会执行成功的回调函数,如果想执行失败状态的回调函数需要自己手动return 失败状态的Promise对象,除了这种方法外还有一种方法,见代码:
new Promise((resolve, reject) => {
// resolve(123);
reject('reason');
})
.then(data => {
console.log(data);
}) /// 第一块
.catch(err => {
console.log(err);
throw new Error('reason');
})
.then(data => {
console.log(data);
}) 第二块
.catch(err => {
console.log(err);
}); /
// reason
// Error: reason
reason是第一块代码执行的结果, Error: reason是第二块代码执行的结果。在第一块代码里扔一个错误,那么就会被第二块代码中的catch捕获到,这样也不会再执行then里面的成功状态的回调函数了。
Promise.resolve() 和 Promise.reject():
1、Promise.resolve():
是成功状态 Promise 的一种简写形式。
new Promise(resolve => resolve('foo'));
// 这个地方的resolve是一个形参,名字想怎么写就怎么写
// 简写
Promise.resolve('foo');
// 这里的resolve是一个固定的方法名,不能乱写
参数:
(1)一般参数(最应该关注):
Promise.resolve('foo').then(data => {
console.log(data);
}); // foo
(2)特殊参数(简单了解一下即可,不要深究):
1、把Promise当作参数传进去。
const p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '我执行了');
});
Promise.resolve(p1).then(data => {
console.log(data);
}); // 我执行了(注:在一秒后打印)
这是为什么呢?原因是:当 Promise.resolve() 接收的是 Promise 对象时,直接返回这个 Promise 对象,什么都不做。
即上面的代码等同于:
const p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '我执行了');
// 上面的setTimeout等同于:
// setTimeout(() => {
// resolve('我执行了');
// }, 1000);
});
p1.then(data => {
console.log(data);
}); // 我执行了(注:一秒后打印)
console.log(Promise.resolve(p1) === p1); // true
还有就是,当 resolve 函数接收的是 Promise 对象时,后面的 then 会根据传递的 Promise 对象的状态变化决定执行哪一个回调。
上代码进行描述:
const p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '我执行了');
});
new Promise(resolve => resolve(p1)).then(data => {
console.log(data);
});
上面那句话的意思就是如果函数接收的是一个Promise对象(即上面代码的p1)时,后面的then受p1支配,因为p1在一秒后执行了resolve('我执行了');
所以就给then里面传了个 ‘我执行了’ ,所以then执行后打印出 我执行了。
2、具有 then 方法的对象:
因这种形式在实际开发中不咋用,这里不再细讲,大家知道有这个东西就行。(要是碰到了直接查文档就完事了)
2、Promise.reject():
失败状态 Promise 的一种简写形式。
new Promise((resolve, reject) => {
reject('reason');
});
// 等价于
Promise.reject('reason');
参数:
不管什么参数,都会原封不动地向后传递,作为后续方法的参数。(跟resolve相比简单多了)
还有一个Promise.resolve() 和 Promise.reject()的用处就是用在then方法的return中,如:
new Promise((resolve, rejcet) => {
resolve(123);
})
.then(data => {
// return data;
// return Promise.resolve(data); // 传失败状态的Promise对象的简写
return Promise.reject('reason'); // 传失败状态的Promise对象的简写
})
.then(data => {
console.log(data);
})
.catch(err => console.log(err));
// reason
Promise.all():
1、有什么用:
Promise.all() 关注多个 Promise 对象的状态变化;可以在Promise对象中传入多个 Promise 实例,包装成一个新的 Promise 实例返回。
2、基本用法:
Promise.all() 的状态变化与所有传入的 Promise 实例对象状态有关
所有状态都变成 resolved,最终的状态才会变成 resolved
只要有一个变成 rejected,最终的状态就变成 rejected
见代码:
// 封装一个延迟函数:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1'; // 默认返回成功状态的Promise对象
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return 'p2'; // 默认返回成功状态的Promise对象
});
const p = Promise.all([p1, p2]);
p.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
上面代码中的执行结果是:p1完成了(一秒后),p2完成了 (2) [“p1”, “p2”] (两秒后)
这块代码是最终的状态是 resolved
再看下面的代码:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return Promise.reject('reason');
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return 'p2';
});
const p = Promise.all([p1, p2]);
p.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
这块代码的结果是:p1完成了 reason (注:一秒后打印) p2完成了 (注:两秒后打印)
这块代码中有rejected,所以最终状态就是rejected,可能大家疑惑为什么reason在 p1完成了 后面出现,大家可以这样理解:因为系统检测到某个地方出现了rejected,所以直接判定Promise.all的状态是rejected,所以直接执行p后面then方法的第二个回调函数,所以打印出reason,又因为那个rejected的状态是在p1中检测到的,所以reason出现在 p1完成了 的后面。
大家可以通过下面的例子检测字迹是否理解:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1';
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
});
const p = Promise.all([p1, p2]);
p.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
这里的结果是:p1完成了(注:一秒后打印出来) p2完成了 reason(注:两秒后打印出来)
Promise.race() 和 Promise.allSettled():
先声明一下,这两个方法大家简单了解一下即可。
1、Promise.race():
Promise.race() 的状态取决于第一个完成的 Promise 实例对象,如果第一个完成的成功了,那最终的就成功;如果第一个完成的失败了,那最终的就失败。
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1';
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return 'p2';
});
const racePromise = Promise.race([p1, p2]);
racePromise.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1';
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
});
const racePromise = Promise.race([p1, p2]);
racePromise.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
这里提供两个例子,供大家自己自行下去进行验证学习,这里就不细讲了,大家在自己编译器上一运行就一目了然了。
2、Promise.allSettled():
Promise.allSettled() 的状态与传入的Promise 状态无关,它后面的then方法永远执行成功的回调函数,它只会忠实的记录下各个 Promise 的表现。见代码:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1';
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
});
const allSettledPromise = Promise.allSettled([p1, p2]);
allSettledPromise.then(data => {
console.log('succ', data);
});
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return Promise.reject('reason');
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
});
const allSettledPromise = Promise.allSettled([p1, p2]);
allSettledPromise.then(data => {
console.log('succ', data);
});
上面两个例子大家自行演示,看到结果大家都会明白的。
Promise的注意事项:
1、resolve 或 reject 函数执行后的代码:
推荐在调用 resolve 或 reject 函数的时候加上 return,不再执行它们后面的代码。
如:
new Promise((resolve, reject) => {
// // return resolve(123);
return reject('reason');
});
虽然 resolve 或 reject 函数的后面的代码仍可以执行。如:
new Promise((resolve, reject) => {
// // return resolve(123);
return reject('reason');
console.log('hi');
}); // hi
但是不建议这样写。
2、Promise.all/race/allSettled 的参数问题:
参数如果不是 Promise对象 的数组,系统会默认的将不是 Promise 的数组元素转变成 Promise对象 的数组。
Promise.all([1, 2, 3]).then(datas => {
console.log(datas);
});
// 等价于
Promise.all([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
]).then(datas => {
console.log(datas);
});
参数不只是数组,任何可遍历的都可以作为参数:
如:原生可遍历的:数组、字符串、Set、Map、NodeList、arguments
和非原生可遍历的(通过Iterate)
举个栗子:
// Set作为参数
Promise.all(new Set([1, 2, 3])).then(datas => {
console.log(datas);
});
3、Promise.all/race/allSettled 的错误处理:
单独处理:
// 第一个案例:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return Promise.reject('reason');
}).catch(err => {
console.log('p1', err);
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
}).catch(err => {
console.log('p2', err);
});
const allPromise = Promise.all([p1, p2]);
allPromise.then(datas => {
console.log(datas);
});
统一处理:
// 第二个案例:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return Promise.reject('reason');
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
});
const allPromise = Promise.all([p1, p2]);
allPromise.then(datas => {
console.log(datas);
}).catch(err => console.log(err));
错误既可以单独处理,也可以统一处理
一旦被处理,就不会在其他地方再处理一遍(上面第二个案例)