Promise + async/await【JS深入知识汇点8】
JS 是一个单线程的语言,按照连续顺序依次执行。但是当浏览器加载一些网络请求,由于是单线程,需要等待这些内容访问完才可以执行下面的代码。那么这段时间就什么都做不了,这种效果对于程序而言,是一种阻塞,这个时候异步就出现了。在等待时间内,选择让程序继续,在等待时间结束的时候,通知我们的程序执行完毕,这就是异步。
Promise
Promise 是异步编程的一种解决方案,它是一个对象,可以获取异步操作的信息。
Promise 特点
- 对象的状态不受外界影响,Promise 有三种状态:pending、fulfilled、rejected,只有异步操作的结果,可以决定当前是哪一种状态
- 一旦状态改变,就不会再变
- 无法取消 Promise,一旦新建,它就会立即执行,无法中途取消
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部
- 当处于 pending 时,无法得知目前发展到哪一个阶段
Promise 用法
Promise 对象是一个构造函数,用来生成 Promise 实例。
- 创建一个 Promise 实例,创建后就会立即执行
// resolve 和 reject 是两个函数,由 JS 引擎提供 const promise = new Promise((resolve, reject) => {...})
- 用
.then
方法分别指定实例resolved
状态和rejected
状态的回调函数。将在当前脚本所有同步任务执行完才会执行。promise.then(function(value) {...success}, function(error) {...failure})
reject
参数一般是Error
对象的实例。resolve
函数的参数除了正常值,还可能是另外一个实例promise2
,此时的promise2
的状态决定promise
的状态const p1 = new Promise((resolve, reject) => { setTimeout(() => reject(new Error('fail')), 3000) }) // p2 返回的是另外一个 promise,导致 p2 自己的状态无效了,由 p1 状态决定 p2 的状态 const p2 = new Promise((resolve, reject) => { setTimeout(() => resolve(p1), 1000) }) p2.then(result => console.log(result), error => console.log(error))
- 调用
resolve
和reject
并不会终结Promise
的参数函数的执行,会继续执行下面的语句,如果调用了return
语句,就不会执行了 then
方法返回的是一个Promise
实例(不是原来那个),所以可以使用链式写法,第一个回调函数完成后,会将返回结果作为参数,传入第二个回调函数。catch
用于指定发生错误时的回调函数。- 如果
Promise
状态已经改变状态,再抛出错误,是不会被捕获的。 Promise
对象具有“冒泡”性质,会一直向后传递,直到被捕获为止。catch
返回的是一个Promise
对象,后面还可以接着调用then
- 在链式
promise
里,如果没有报错,就可以跳过catch
,直接执行后面的then
,要是此时then
里报错,就和前面的catch
无关了 catch
里抛出一个错误的话,如果后面有catch
,就会跳过then
,被catch
捕获,否则不会被捕获,也不会被传递到外层。- 如果没有使用
catch()
指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。 - promise 内部的错误不会影响到外部,外部代码依旧会继续执行
- node.js 有一个
unhandledRejection
事件,专门监听未捕获的 reject 错误,
- 如果
- finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。finally 不接受任何参数,意味着没有办法知道之前的
Promise
状态到底是fulfilled
还是rejected
ES6 Promise 的用法
Promise.all([p1, p2, p3])
用于将多个 Promise 实例,包装成一个新 Promise 实例
- 只有p1、p2、p3 的状态都变成
fulfilled
,p的状态才会变成fulfilled
,此时p1、p2、p3的返回值组成一个数组,传递给 p 的回调函数 - 只要 p1、p2、p3 中有一个被
rejected
,p的状态就会变成rejected
,第一个被reject
的实例返回值,会传给 p 的回调函数 - 如果参数不是 Promise 实例,就会调用
Promise.resolve()
- 如果作为参数的 promise 实例,自己定义了catch,那么它一旦被
rejected
,就不会触发Promise.all
的catch
,会触发then
Promise.race([p1, p2, p3])
用于将多个 Promise 实例,包装成一个新 Promise 实例
- p1、p2、p3中,只要有一个实例率先改变状态,p的状态就跟着改变,率先改变状态的 Promise 实例的返回值,传给 p 的回调函数
- 如果参数不是 Promise 实例,就会调用 Promise.resolve()方法
Promise.allSettled([p1, p2, p3])
用于将多个 Promise 实例作为参数,包装成一个新的 Promise 实例,只有等到所有参数实例都返回结果,不管是 fulfilled 还是 rejected,包装实例才会结束。
接收到的返回值格式:
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.any([p1, p2, p3])
用于将多个 Promise 实例,包装成一个新 Promise 实例。
- 只要有一个参数变成 fulfilled,就会变成 fulfilled,如果所有的参数变成 rejected,包装实例就会变成 rejected 状态
- 它和 promise.race() 很像,只有一点不同,就是不会因为某个 Promise 变成 rejected 状态而结束。
- 它抛出的错误,不是一般的错误,而是一个 AggregateError 实例,相当于一个数组,每个成员对应一个 rejected 的操作所抛出的错误。
Promise.resolve()
可以用来将现有对象转成 Promise 对象,状态是 resolved。
Promise.resolve('foo')
// 等价于
new Promise((resolve) => resolve('foo'))
- 参数是 promise,将原封不动的返回这个实例
- 参数是一个 thenable 对象,会将这个对象转为 promise 对象,并且立即执行 thenable 对象的 then 方法
- 参数是个不具有 then 方法的对象,或不是对象,则会返回一个新的 Promise 对象,状态为 resolved .
- 不带任何参数,直接返回一个 resolved 状态的 Promise 对象,并在本轮“事件循环”的结束时执行
setTimeout(() => {console.log('one')}, 0);
Promise.resolve().then(() => {
console.log('two')
setTimeout(() => console.log('three'),0)
})
console.log('four')
// 执行顺序是:four two one three
Promise.reject()
用来返回一个 Promise 对象,状态是 rejected。 Promise.reject() 的参数,会 原封不动 地作为 reject 的理由,变成后续方法的参数。
Promise.try()
当then内的函数是不区分同步或异步的,它会在本轮事件循环的末尾执行。
const f = () => console.log('now');
Promise.resolve().then(f)
console.log('next')
// 执行顺序:next now
让同步函数同步执行,异步函数异步执行的方法: 1.用 async 函数来写
const f = () => console.log('now');
(async () => f())().then(() => {})
console.log('next')
// 执行顺序: now next
2.用 new Promise()
const f = () => console.log('now')
(
() => new Promise((resolve, reject) => {
resolve(f())
})
)()
console.log('next')
// 执行顺序: now next
鉴于这是一个很常见的需求,所以现在有一个提案,提供 Promise.try() 的方法替代上面的写法。
const f = () => console.log('now');
Promise.try(f)
console.log('next')
// 执行顺序: now next
Async / Await
async 函数就是 Generator 的语法糖,将 * 替换成 async,将 yield 替换成 await。 async 特性:
- async 表示函数里有异步操作, await 表示紧跟在后面的表达式需要等待结果
- async 函数的返回值是 Promise 对象,可以用 then 方法添加回调函数,此时then的参数是函数内部 return 语句返回的值。
- 当函数执行时,一旦遇到 await就会先暂停,等到异步操作完成,再接着执行函数体内后面的语句。
- async 函数返回的 promise 对象,必须等到内部所有 await 命令后面的 promise 对象执行完,才会发生状态变化,执行 then 方法指定的回调函数。
await 特性:
- await命令后面是一个 promise 对象,返回该对象的结果,如果不是 promise 对象或者 thenable 对象,就直接返回对应的值。
- await命令后面的 Promise 对象如果变成 reject 状态,则会被 async 函数的catch捕获到。
- 任何一个 await 后的 promise 对象变为 reject 状态,那么整个 async 函数都会中断执行。
- 可以用 Promise.all,让并行的 异步请求同时执行。
写一个符合 Promises/A+ 规范的 promise
Promises/A+ 规范在网上搜一搜就可以得到,附上链接,或自行谷歌.
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function Promise(f) {
this.state = PENDING;
// state = fulfilled 时,result 是 value
// state = rejected 时,result 是 reason
this.result = null;
// then 方法可被调用多次,callbacks 用来记录每次注册的 onFulfilled 和 onRejected 的callback
this.callbacks = [];
let onFulfilled = value => transition(this, FULFILLED, value);
let onRejected = reason => transition(this, REJECTED, reason);
// 通过ignore 保证 resolve/reject 只有一次调用作用
let ignore = false;
let resolve = value => {
if (ignore) return ;
ignore = true;
resolvePromise(this, value, onFulfilled, onRejected)
}
let reject = reason => {
if (ignore) return;
ignore = true;
onRejected(reason)
}
try {
f(resolve, reject)
} catch (error) {
reject(error)
}
}
// 单个 promise 状态迁移函数
const transition = (promise, state, result) => {
if (promise.state !== PENDING) return;
promise.state = state;
promise.result = result;
setTimeout(() => handleCallbacks(promise.callbacks, state, result), 0)
}
const handleCallbacks = function(callbacks, state, result) {
while (callbacks.length) handleCallback(callbacks.shift(), state, result)
}
// 在当前 promise 和下一个 promise 之间进行状态传递
const handleCallback = (callback, state, result) => {
let {onFulfilled. onRejected, resolve, reject} = callback;
try {
if (state === FULFILLED) {
isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
} else if (state === REJECTED) {
isFunction(onRejected) ? resolve(onRejected(result)): reject(result)
}
} catch (error) {
reject(error)
}
}
// 必须有 then 方法,接受 onFulfilled 和 onRejected 参数,并返回 promise
Promise.prototype.then = function(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
let callback = {onFulfilled, onRejected, resolve, reject};
if (this.state === PENDING) {
this.callbacks.push(callback)
} else {
setTimeout(() => handleCallback(callback, this.state, this.result), 0)
}
})
}
// 对特殊的 result 进行特殊处理
const resolvePromise = (promise, result, resolve, reject) => {
if (result === promise) {
let reason = new TypeError('Can not fulfill promise with itself');
return reject(reason)
}
if (isPromise(result)) {
return result.then(resolve, reject)
}
if(isThenable(result)) {
try {
let then = result.then;
if (isFunction(then)) {
return new Promise(then.bind(result)).then(resilve, reject)
}
} catch (error) {
return reject(error)
}
}
resolve(result)
}
Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}
Promise.resolve = value => new Promise(resolve => resolve(value))
Promise.reject = reason => new Promise((_,reject) => reject(reason))
转载自:https://juejin.cn/post/6859286605187383303