likes
comments
collection
share

Promise + async/await【JS深入知识汇点8】

作者站长头像
站长
· 阅读数 0

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))
    
  • 调用 resolvereject 并不会终结 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.allcatch,会触发 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))

源码