Promise基础总结

1、promise是什么

  • “生产者代码(producing code)”会做一些事,并需要一些时间。例如,通过网络加载数据的代码。
  • “消费者代码(consuming code)”想要在生产者代码完成工作的第一时间就能获取其工作成果。许多函数可能都需要这个结果。
  • Promise是将“生产者代码”和“消费者代码”连接在一起的一个特殊JavaScript对象。

Promise对象的构造器(constructor)语法如下

let promise = new Promise(function(resolve, reject) {
	// executor(生产者代码)
});

其中,传递给new Promise的函数被称为executor。当new Promise被创建,executor会自动执行。它包含最终应产出结果的生产者代码。
参数resolvereject是由JavaScript自身提供的回调,我们自己的代码仅在executor内部。

当executor获得结果,应该调用以下回调之一:

  • resolve(value) - 任务成功完成并带有结果value
  • reject(error) - 如果出现了error,error 即为error对象

由new Promise构造器返回的promise对象具有以下的内部属性:

  • state - 最初是pending,然后在resolve被调用时变为fulfilled,或者在reject被调用时变为rejected。
  • result - 最初时undefined,然后在resolve(value)被调用时变为value,或者在reject(error)被调用时变为error。

与最初的"pedding" promise相反,一个resolved或rejected的promise都会被称为"settled"。
promise对象中state和result属性都是内部的。无法直接访问,但可以对其使用.then/.catch/.finally方法。

2、消费者:then,catch

promise对象是executor和consumer之间的连接,后者将接收result或error,可以通过.then和.catch方法注册消费函数。

2.1 then

最基础最重要的就是then

promise.then (
	function(result) {/* handle a successful result */},
	function(error) {/* handle an error */}
);

then的两个参数都是函数。

  • 第一个参数将在promise resolved且接收到结果后执行;
  • 第二个参数将在promise rejected且接收到error信息后执行。
let promise = new Promise(function(resolve, reject) {
	setTimeout(() => resolve("done!"), 1000);
});

// resolve 运行 .then的第一个函数
promise.then(
	result => alert(result),  // 1s后显示done
	error => alert(error)  // 不运行
);

如果是reject的情况:

let promise = new Promise(function(resolve, reject) {
	setTimeout(() => reject(new Error("oh no, it's an error!")), 1000);
});

如果只对成功完成的情况感兴趣,则可以只为.then提供一个函数参数:

promise.then(alert);
2.2 catch

如果只对error感兴趣,那么可以将第一个函数参数设置为null:

.then(null, errorHandlingFunction)

或者使用.catch(errorHandlingFunction)

promise.catch(alert);
// catch(f) 与 .then(null, f)一样

3、清理 finally

像常规try {...} catch {...}中的finally子句一样,promise中也有finally。
调用.finally(f)类似于.then(f, f), 因为当promise 状态为settled时,f 就会执行:无论promise被resolve还是reject。
finally的功能是设置一个处理程序在前面的操作完成后,执行清理/终结。例如,停止加载指示器,关闭不再需要的连接等。

new Promise((resolve, reject) => {
	// executor code... 之后调用可能会 resolve,也可能会reject
})
// 在promise为 settled 时运行(无论是resolved还是rejected)
.finally(() => stop loading indicator)
// 所以加载指示器 loading indicator始终会在我们继续之前停止
.then(result => show result, err => show error)

finally(f) 和 then(f, f)执行有重要的区别

  1. finally处理程序没有参数,finally中,我们并不知道promise是否成功。
  2. finally处理程序将result或error传递给下一个合适的处理程序。
    例如上述例子中,传递给了then。
    也就是说,finally并不会处理promise的结果,无论结果如何,都会进行常规的清理。
  3. finally处理程序也不应该返回任何内容。如果返回了,返回值也会默认被忽略。
    此规则唯一例外是当finally处理程序抛出error,这个error(不是任何之前的结果)会被转到下一个处理程序。

4、Promise链

可以通过.then处理程序链进行传递result。

new Promise((resolve, reject) => {
	setTimeout(() => resolve(1), 1000);
}).then(function(result) {
	alert(result);  // 1
	return result * 2;
}).then(function(result) {
	alert(result);  // 2
	return result * 2;
}).then(function(result) {
	alert(result);  // 4
	return result * 2;
})

每个对.then的调用都会返回一个新的promise,因此可以在其之上调用下一个.then。
当然,也可以将多个.then添加到一个promise上,但这并不是promise链chaining。


then处理程序中,还可以创建并返回一个promise

new Promise((resolve, reject) => {
	setTimeout(() => resolve(1), 1000);
}).then(function(result) {
	alert(result);  // 1
	return new Promise((resolve, reject) => {
	setTimeout(() => resolve(result * 2), 1000);
})
}).then(function(result) {
	alert(result);  // 2
	return new Promise((resolve, reject) => {
	setTimeout(() => resolve(result * 2), 1000);
})
}).then(function(result) {
	alert(result);  // 4
})

以上程序每隔1s弹出一个数字,then处理程序返回一个promise对象,会将result传递给下一个then处理程序。
确切地说,处理程序返回的不完全是一个promise,而是返回一个被称为“thenable”对象—— 一个具有.then方法的任意对象,它会被当作一个promise来对待。

5、fetch

promise经常被用于网络请求,fetch方法用于从远程服务器加载用户信息。

let promise = fetch(url);

执行这条语句,向url发出网络请求并返回一个promise。当远程服务器返回header(是在 全部相应加载完成前)时,该promise使用一个response对象来进行resolve。
为了读取完整的响应,我们应该调用 response.text() 方法:当全部文字内容从远程服务器下载完成后,会返回一个promise,该promise以刚刚下载完成的这个文本作为result进行resolve。

fetch('url')
.then(function(response) {
  // 当url加载完成时,response.text()会返回一个新的promise对象
	return response.text();
}).then(function(text) {
	alert(text);
})

从fetch返回的response对象还包括response.json()方法,该方法读取远程数据并将其解析为JSON。

fetch('url')
.then(response => response.json())
.then(user => alert(user.name));

6、then vs catch

promise.then(f1).catch(f2);
vs
promise.then(f1, f2);
第一个例子中f1如果出现error,会被catch处理;但第二个例子不会。
因为error是沿着链传递的,第二个代码的f1下面没有链。换句话说,.then将result/error传递给下一个.then/.catch。

7、使用promise进行错误处理

Promise链在错误处理中十分强大,当一个promise被reject时,控制权将移交至最近的rejection处理程序。
.catch不必是立即的,可以在一个或多个.then之后出现。捕获所有error最简单的方法是,将.catch附加到链的末尾。通常情况下,.catch不会被触发,但是如果上述任意一个promise被reject(网络问题或者无效的json或其他),.catch就会捕获它。
隐式try…catch
Promise的执行者(executor)和promise的处理程序(handler)周围有一个隐式的try…catch,如果发生异常,则会被捕获,并被视为rejection进行处理。

new Promise((resolve, reject) => {
	throw new Error("oh no, it's an error!");
}).catch(alert);

以上代码和下面的代码工作上完全相同:

new Promise((resolve, reject) => {
	reject(new Error("oh no, it's an error"));
}).catch(alert);

在executor周围的隐式try…catch自动捕获了error,并将其变为rejected promise。
这不仅仅发生在executor函数中,同样也发生在其handler中。如果在.then处理程序中throw,promise对象也会被rejected,控制权移交至最近的error处理程序。

new Promise(function(resolve, reject) {
  setTimeout(() => {
    throw new Error("Whoops!");
  }, 1000);
}).catch(alert);

但是上述的.catch不会被触发,因为函数代码周围虽然有隐式的try…catch,所有的同步错误都会得到处理。但是!上述代码中,错误并不是在executor运行时生成的,而是稍后生成的,因此promise无法进行处理。

8、Promise API

8.1 Promise.all

假设我们希望并行执行多个promise,并等待所有promise都准备就绪。例如,并行下载几个URL并等到所有内容都下载完毕后再对它们进行处理。
let promise = Promise.all(iterable);
这里接受一个可迭代对象,通常是一个数组项为promise的数组,并返回一个新的promise。当所有给定的promise都resolve时,新的promise才会resolve,并将其结果数组作为新promise的结果。

Promise.all([
	new Promise(resolve => setTimeout(() => resolve(1), 3000)),
	new Promise(resolve => setTimeout(() => resolve(2), 2000)),
	new Promise(resolve => setTimeout(() => resolve(3), 1000))
]).then(alert);  // 1,2,3 

结果数组中元素的顺序与其在源promise中的顺序相同。即使第一个promise花费了最长的时间才resolve,但它仍是结果数组中的第一个。
一个常见的技巧是,将一个任务数组映射到一个promise数组,然后将其包装到Promise.all:

let urls = [
	'url1',
	'url2',
	'url3'
];
// 将每个url映射到fetch到Promise中
let requests = urls.map(url => fetch(url));
// Promise.all 等待所有任务都 resolved
Promise.all(requests)
.then(responses => responses.forEach(
	response => alert(`${response.url}: ${response.status}`)
));

在Promise.all()中,如果任意一个promise被reject,则返回的promise会立即reject,并带有对应被reject的那个promise的error。也就是说,如果出现了error,那么其他的promise就会被忽略掉。
Promise.all(iterable)也允许参数是非promise的常规值,该参数将被按原样传递给结果数组。

Promise.all([
	new Promise((resolve, reject) => {
		setTimeout(() => resolve(1), 1000)
	}),
	2,
	3
]).then(alert);  // 1,2,3
8.2 Promise.allSettled

该API等待所有的promise都被settle(无论结果如何)后,返回一个结果数组:

  • 对于成功的响应:{status: “fulfilled”, value: result}
  • 对于error:{status: “rejected”, reason: error}
8.3 Promise.race

只等待第一个settled的promise并获取其结果/error

8.4 Promise.any

只等待第一个fulfilled的promise,并将其返回。如果给出的promise都rejected,则返回rejected的promise和AggregateError错误类型的error实例—— 一个特殊的error对象,在其errors属性中存储着所有promise error。

8.5 Promise.resolve/reject

使用给定的value创建一个resolved/rejected的promise。

9、微任务Microtask

Promise的处理程序.then, .catch, .finally都是异步的。
即便一个promise立即被resolve,这些处理程序下面的代码也会在这些处理程序之前就被执行。

let promise = Promise.resolve();
promise.then(() => alert("promise done!"));
alert("code done!");

以上代码会先弹出"code done!“,然后再弹出"promise done!”。这是因为微任务队列Microtask queue。
异步任务需要适当的管理。为此,ECMA标准规定了一个内部队列PromiseJobs,通常被称为“微任务队列”。
队列queue是先进先出的:首先进入队列的任务会首先执行。
只有在js引擎中没有其他任务运行时,才开始执行任务队列中的任务。
简单来说,当一个promise准备就绪时,.then/catch/finally处理程序就会被放入队列,但不会立即被执行。js引擎执行完当前代码后,才会从队列中获取任务并执行。
如果需要确保一段代码在处理程序之后被执行,可以将它添加到链式调用的.then中。

10、Async/await

10.1 Async function

在函数前的async就表达了一个简单的事情:这个函数总是返回一个promise,其他值将自动被包装在一个resolved的promise中。

async function f() {
	return 1;
}
f().then(alert);  // 1

也可以显式返回一个promise,与上述一样:

async function f() {
	return Promise.resolve(1);
}
f().then(alert);
10.2 Await

只能在async函数内工作。
关键字await让js引擎等待直到promise完成settle并返回结果。

async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise;  // 等待,直到promise resolve
alert(result);  // "done!"
}
f();

函数执行时,直到promise settle,拿到result作为结果继续往下执行。await实际上会暂停函数的执行,直到promise状态变为settled,然后以promise的结果继续执行。该行为不会耗费任何CPU资源,因为js引擎可以同时处理其他任务,比如:执行其他脚本,处理事件等。
相比于promise.then,它只是获取promise的结果的一个更优雅的语法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值