2020-0305
请求串行执行:
function request_one() {return new Promise((resolve) => {setTimeout(() => {console.log("request_one_data");resolve("one");}, 3000);});}function request_two(res1) {return new Promise((resolve) => {setTimeout(() => {console.log("request_two_data");resolve(res1 + " " + "two");}, 2000);});}request_one().then((res) => {return request_two(res);}).then((res) => {console.log(res);});
注意,不要写错:
// 以下是错误的写法:function request_two(res1) {setTimeout(() => { // 错误,setTimeout应该在new Promise的里面,是在里面模拟异步请求!return new Promise((resolve) => {console.log("request_two_data");resolve(res1 + " " + "two");});}, 2000);}
请求并行执行,(一起执行,都resolved才返回)使用Promise.all方法:
var promises = function () {return [1000, 2000, 3000].map(current => {return new Promise(function (resolve, reject) {setTimeout(() => {console.log(current)}, current)})})}Promise.all(promises()).then(() => {console.log('end')})
自行对比与async/await写法的异同。
MDN写的例子很好,几种情况都有:MDN例子__串行、并行等.js
有篇博文对其进行了封装,Promise的并行和串行
自己写的简单例子:语法简单例子.js
2020-08-26
Promise的特点
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise的作用
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
Promise的缺点
(1)无法取消Promise,一旦新建它就会立即执行;
(2)没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码;
基本用法
const promise = new Promise(function(resolve, reject) {// ... some codeif (/* 异步操作成功 */){resolve(value);} else {reject(error);}});promise.then(function(value) {// success}, function(error) {// failure});
Promise 新建后就会立即执行。
异步加载图片的例子:
function loadImageAsync(url) {return new Promise(function(resolve, reject) {const image = new Image();image.onload = function() {resolve(image);};image.onerror = function() {reject(new Error('Could not load image at ' + url));};image.src = url;});}
如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数,即then()和catch()函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例。
const p1 = new Promise(function (resolve, reject) {// ...});const p2 = new Promise(function (resolve, reject) {// ...resolve(p1);})// 此时p1的状态会决定p2的状态
注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。
new Promise((resolve, reject) => {resolve(1); // ''' 若加上return, 则不会执行后面的语句console.log(2); // '先输出2}).then(r => {console.log(r); // ''再输出1});// 2// 1
Promise.prototype.then()
采用链式的then,可以指定一组按照次序调用的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。通常只写第一个参数。
Promise.prototype.catch()
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
getJSON('/posts.json').then(function(posts) {// ...}).catch(function(error) {// 处理 getJSON 和 前一个回调函数运行时发生的错误console.log('发生错误!', error);});
// 写法一const promise = new Promise(function(resolve, reject) {try {throw new Error('test');} catch(e) {reject(e);}});// 写法二与一等价const promise = new Promise(function(resolve, reject) {reject(new Error('test'));});promise.catch(function(error) {console.log(error);});
如果 Promise 状态已经变成resolved,再抛出错误是无效的。因为 Promise 的状态一旦改变,就永久保持该状态。
Promise 内部的错误不会影响到 Promise 外部的代码:
const someAsyncThing = function() {return new Promise(function(resolve, reject) {// 下面一行会报错,因为x没有声明resolve(x + 2); // 控制台会抛出错误,进入rejected状态,但依旧往下执行});};someAsyncThing().then(function() {console.log('everything is great');});setTimeout(() => { console.log(123) }, 2000); // 2秒后还是会输出// Uncaught (in promise) ReferenceError: x is not defined// 123
Node.js有一个unhandledRejection事件(未来会废除,更改为终止进程且退出码不为0),专门监听未捕获的reject错误:
process.on('unhandledRejection', function (err, p) {throw err;});
Promise.prototype.finally()
不管 Promise 对象最后状态如何,都会执行的操作。
promise.then(result => {···}).catch(error => {···}).finally(() => {···});
Promise.all()
Promise.all()方法将多个 Promise 实例,包装成一个新的 Promise 实例。resolve回调执行是在所有输入的promise的resolve回调都结束;reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。
const p = Promise.all([p1, p2, p3]);
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)任意一个为rejected状态,则p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
注意,如果作为参数的 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。如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。
Promise.race()
只要有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
const p = Promise.race([p1, p2, p3]);
Promise.allSettled()
接受一组 Promise 实例作为参数,只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020 引入。Promise.allSettled()状态只可能变成fulfilled。
const resolved = Promise.resolve(42);const rejected = Promise.reject(-1);const allSettledPromise = Promise.allSettled([resolved, rejected]);allSettledPromise.then(function (results) {console.log(results);});// [// { status: 'fulfilled', value: 42 }, // 注意返回的格式// { status: 'rejected', reason: -1 }// ]
实际用处:若不关心异步操作的结果,只关心这些操作有没有结束。Promise.allSettled()方法就很有用。
Promise.any()
只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。是一个第三阶段的提案 。Promise.any()抛出的错误,是一个 AggregateError实例。它相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。
var resolved = Promise.resolve(42);var rejected = Promise.reject(-1);var alsoRejected = Promise.reject(Infinity);Promise.any([resolved, rejected, alsoRejected]).then(function (result) {console.log(result); // 42});Promise.any([rejected, alsoRejected]).catch(function (results) {console.log(results); // [-1, Infinity]});
Promise.resolve()
该方法将现有对象转为 Promise 对象。
传入的参数分成4中情况:
- 参数为Promise实例:原封不动返回这个实例;
- 参数为具有then方法的对象:会将该对象转为Promise对象并立即执行then方法;
- 参数是原始值或不带then方法的对象:返回新的Promise对象,状态为resolved;
- 不带任何参数:则直接返回一个resolved状态的Promise对象;
注意:立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是下一轮开始时:
setTimeout(function () {console.log('three');}, 0);Promise.resolve().then(function () {console.log('two');});console.log('one');// one// two// three
Promise.reject()
该方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。
Promise.try()
经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法:
Promise.resolve().then(f)
但该方法有个缺点:若f是同步函数,则会在本轮事件循环末尾处进行,变成了异步执行。
那有没有方法,让同步函数同步执行,异步函数异步执行,且让它们有共同API?2种写法:
async:
const f = () => console.log('now');(async () => f())();console.log('next');// now// next
使用
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
好处之一是可以更好地管理异常:
function getUsername(userId) {return database.users.get({id: userId}).then(function(user) {return user.name;});}
database.users.get()抛出一个异步错误,可用catch方法捕获;但也可能抛出同步错误(如数据库连接错误),不得的用try...catch去捕获错误。即变成:// 很笨拙的写法try {database.users.get({id: userId}).then(...).catch(...)} catch (e) {// ...}// 换成Prmise.try(),统一用catch方法捕获错误Promise.try(() => database.users.get({id: userId})).then(...).catch(...)
