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 code
if (/* 异步操作成功 */){
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(...)