Promise 从入?到精通
2021/9/20 6:06:40
本文主要是介绍Promise 从入?到精通,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一. 为什么需要 promise ?
Javascript 是一⻔单线程语言,所以早期我们解决异步的场景时,大部分情况都是通过回调函数来进
行。
例如在浏览器中发送 ajax 请求,就是常⻅的一个异步场景,发送请求后,一段时间服务端响应之后我们 才能拿到结果。如果我们希望在异步结束之后执行某个操作,就只能通过回调函数这样的方式进行操 作。
var dynamicFunc = function(cb) { setTimeout(function() { cb(); }, 1000); } dynamicFunc(function() {console.log(123)});
- 上面这个例子的 dynamicFunc 就是一个异步的函数
- 里面执行的 setTimeout 会在 1s 之后调用传入的 cb 函数
- 照上面的调用方式,最终 1s 之后,会打印 123 这个结果
同样的,如果后续还有内容需要在异步函数结束时输出的话,就需要多个异步函数进行嵌套,非常不利 于后续的维护。
setTimeout(function() { console.log(123); setTimeout(function() { console.log(321); // ... }, 2000); }, 1000);
为了能使回调函数以更优雅的方式进行调用,在 ES6 中 js 产生了一个名为 promise 的新规范,他让异步操作的变得近乎「同步化」。
二. Promise 基础
在支持 ES6 的高级浏览器环境中,我们通过 Promise()
即可构造一个 promise 实例。
这个构造函数接受一个函数,分别接受两个参数,resolve 和 reject,代表着我们需要改变当前实例的状 态到 已完成
或是 已拒绝
。
function promise1() { return new Promise(function(resolve, reject) { // 定义异步的内容 setTimeout(function() { console.log('1s 后输出'); // 输出完成后,调用函数传入的 resolve 函数,将该 promise 实例标记为已完成,当前 promise 串行继续执行 resolve(); }, 1000); }); }
function promise2() { return new Promise(function(resolve) { setTimeout(function() { console.log('2s 后输出'); resolve(); }, 2000); }); }
上面的两个 promise 实例,串联起来即可写为:
promise1().then(function() { return promise2(); });
也可以简写为 promise1().then(promise2);
浏览器中执行之后,即可看到,1s 之后出现 1s 后输出 字样,再经过 2s 出现 2s 后输出 字样。在这个例子中我们能看到。当前 promise 如果状态变为已完成(执行了 resolve 方法),那么就会去执行 then 方法中的下一个 promise 函数。
同样的,如果我们的 promise 变为已拒绝状态(执行了 reject 方法),那么就会进入后续的异常处理函数中。
function promise3() { return new Promise(function(resolve, reject) { var random = Math.random() * 10; // 随机一个 1 - 10 的数字 setTimeout(function() { if (random >= 5) { resolve(random); } else { reject(random); } }, 1000); }); } var onResolve = function(val) { console.log('已完成:输出的数字是', val); }; var onReject = function(val) { console.log('已拒绝:输出的数字是', val); } // promise 的 then 也可以接受两个函数,第一个参数为 resolve 后执行,第二个函数为 reject 后执行 promise3().then(onResolve, onReject); // 也可以通过 .catch 方法拦截状态变为已拒绝时的 promise promise3().catch(onReject).then(onResolve); // 也可以通过 try catch 进行拦截状态变为已拒绝的 promise try { promise3().then(onResolve); } catch (e) { onReject(e); }
- 这个例子使用了三种方式
拦截
最终变为「已拒绝」
状态的 promise,分别是- 使用 then 的第二个参数
- 使用 .catch 方法捕获前方 promise 抛出的异常
- 使用 try catch 拦截代码块中 promise 抛出的异常
同时我们还可以发现,在改变 promise 状态时调用 resolve 和 reject 函数的时候,也可以给下一步 then 中执行的函数传递参数。这个例子中我们把随机生成的数字传给了 resolve 和 reject 函数,我们也就能在 then 中执行函数的时候拿到这个值。
总结一下本小节的内容:
-
promise 会有三种状态,「进行中」「已完成」和「已拒绝」,进行中状态可以更改为已完成 或 已拒绝,已经更改过状态后无法继续更改(例如从已完成改为已拒绝)。
-
ES6 中的 Promise 构造函数,我们构造之后需要传入一个函数,他接受两个函数参数,执行第一个参数之后就会改变当前 promise 为「已完成」状态,执行第二个参数之后就会变为「已拒绝」 状态。
-
通过 .then 方法,即可在
上一个 promise
达到已完成时继续执行下一个函数或 promise。同时通过 resolve 或 reject 时传入参数,即可给下一个函数或 promise 传入初始值。 -
已拒绝的 promise,后续可以通过 .catch 方法或是 .then 方法的第二个参数或是 try catch 进行捕 获。
三. 如何封装异步操作为 promise
我们可以将任何接受回调的函数封装为一个 promise,下面举几个简单的例子来说明。
// 原函数 function dynamicFunc(cb) { setTimeout(function() { console.log('1s 后显示'); cb(); }, 1000); } var callback = function() { console.log('在异步结束后 log'); } // 用传入回调函数的方式执行 dynamicFunc(callback);
上面的例子就是最传统的,使用传入回调函数的方式在异步结束后执行函数。我们可以通过封装 promise 的方式,将这个异步函数变为 promise。如下:
function dynamicFuncAsync() { return new Promise(function(resolve) { setTimeout(function() { console.log('1s 后显示'); resolve(); }); }); } var callback = function() { console.log('在异步结束后 log'); } dynamicFuncAsync().then(function() { callback() });
再举一个例子,发送 ajax 请求也可以进行封装:
function ajax(url, success, fail) { var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = function() { if (this.readyState !== 4) { return; } if (this.status === 200) { success(this.response); } else { fail(new Error(this.statusText)); } }; client.send(); }; ajax('/ajax.json', function() {console.log('成功')}, function() {console.log('失败')});
我们可以看到,调用 ajax 方法时需要传入 success 和 fail 的回调函数进行调用。我们可以不传入回调函 数,通过封装 promise 的方式,在原来的执行回调函数的地方更改当前 promise 的状态,就可以通过链式调用。
function ajaxAsync(url) { return new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = function() { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; client.send(); }); }; ajax('/ajax.json') .catch(function() { console.log('失败'); }) .then(function() { console.log('成功'); })
总结一下当前小节:
-
我们可以轻松的把任何一个函数或者是异步函数改为 promise,尤其是异步函数,改为 promise 之 后即可进行链式调用,增强可读性。
-
将带有回调函数的异步改为 promise 也很简单,只需要在内部实例化 promise 之后,在原来执行 回调函数的地方执行对应的更改 promise 状态的函数即可。
四. promise 规范解读
任何符合 promise 规范的对象或函数都可以成为 promise,promise A plus 规范地址: https://promisesaplus.com/
上面我们熟悉了整体 promise 的用法,我们知道了如何去创建一个 promise,如何去使用它,后面我们 也熟悉了如何去改造回调函数到 promise。本小节我们详细过一遍 promise A+ 规范
,从规范层面明白 promise 使用过程中的细节。
4.1 术语
Promise: promise 是一个拥有 then
方法的对象或函数,其行为符合本规范
具有 then 方法(thenable): 是一个定义了 then
方法的对象或函数
值(value): 指任何 JavaScript 的合法值(包括 undefined
, thenable 和 promise)
异常(exception): 是使用 throw
语句抛出的一个值
原因(reason): 表示一个 promise 的拒绝原因。
4.2 要求
1. promise 的状态
一个 Promise 的当前状态必须为以下三种状态中的一种: 等待态(Pending)、已完成(Fulfilled)和已拒绝(Rejected) 。
2.必须有一个 then 方法
一个 promise 必须提供一个 then
方法以访问其当前值和原因。
promise 的 then
方法接受两个参数: promise.then(onFulfilled, onRejected)
他们都是可选参数,同时他们都是函数,如果 onFulfilled
或 onRejected
不是函数,则需要忽略他们。
-
如果 onFulfilled 是一个函数
- 当
promise
执行结束后其必须被调用,其第一个参数为promise
的结果 - 在
promise
执行结束前其不可被调用 - 其调用次数不可超过一次
- 当
-
如果 onRejected 是一个函数
- 当
promise
被拒绝执行后其必须被调用,其第一个参数为promise
的原因 - 在
promise
被拒绝执行前其不可被调用 - 其调用次数不可超过一次
- 当
-
在执行上下文堆栈仅包含平台代码之前,不得调用
onFulfilled
或onRejected
-
onFulfilled
和onRejected
必须被作为普通函数调用(即非实例化调用,这样函数内部this
非严格模式下指向 window) -
then
方法可以被同一个promise
调用多次- 当
promise
成功执行时,所有onFulfilled
需按照其注册顺序依次回调 - 当
promise
被拒绝执行时,所有的onRejected
需按照其注册顺序依次回调
- 当
-
then
方法必须返回一个promise 对象
promise2 = promise1.then(onFulfilled, onRejected);- 只要
onFulfilled
或者onRejected
返回一个值x
,promise 2
都会进入onFulfilled
状态 - 如果
onFulfilled
或者onRejected
抛出一个异常e
,则promise2
必须拒绝执行,并返回拒因e
- 如果
onFulfilled
不是函数且promise1
状态变为已完成,promise2
必须成功执行并返回相 同的值 - 如果
onRejected
不是函数且promise1
状态变为已拒绝,promise2
必须执行拒绝回调并返回相同的据因
- 只要
var promise1 = new Promise((resolve, reject) => {reject() }); promise1 .then(null, function() { return 123; }) .then(null, null) .then(null, null) .then( () => { console.log('promise2 已完成'); }, () => { console.log('promise2 已拒绝'); });
五. Promise 构造函数上的静态方法
Promise.resolve
返回一个 promise 实例,并将它的状态设置为已完成,同时将他的结果作为传入 promise 实例的值
var promise = Promise.resolve(123); promise .then(function(val) { console.log('已完成', val); }); // 已完成 123
同样的, Promise.resolve
的参数也可以处理对象,函数等内容,处理方式和上面规范中介绍的相同。
Promise.reject
返回一个 promise 实例,并将它的状态设置为已拒绝,同时也将他的结果作为原因传入 onRejected 函数
var promise = Promise.reject(123); promise .then(null, function(val) { console.log('已拒绝', val); }); // 已拒绝 123
Promise.all
返回一个 promise 实例,接受一个数组,里面含有多个 promise 实例,当所有 promise 实例都成为已完 成状态时,进入已完成状态,否则进入已拒绝状态。
var promise1 = function() { return new Promise(function(resolve) { setTimeout(function() { console.log(1); resolve(); }, 1000) }); } var promise2 = function() { return new Promise(function(resolve) { setTimeout(function() { console.log(2); resolve(); }, 2000); }); } Promise.all([promise1(), promise2()]) .then(function() { console.log('全部 promise 均已完成'); });
注意,此时多个 promise 是同时进行的,也就是在上面这个例子中,等待 1s 打印 1 之后,再等待 1s 就 会打印 2 和「全部 promise 均已完成」。
Promise.race
返回一个 promise 实例,接受一个数组,里面含有多个 promise 实例,当有一个 promise 实例状态改变 时,就进入该状态且不可改变。这里所有的 promise 实例为竞争关系,只选择第一个进入改变状态的 promise 的值。
var promise1 = function() { return new Promise(function(resolve) { setTimeout(function() { console.log(1); resolve(1); }, 1000) }); } var promise2 = function() { return new Promise(function(resolve) { setTimeout(function() { console.log(2); resolve(2); }, 2000); }); } Promise.race([promise1(), promise2()]) .then(function(val) { console.log('有一个 promise 状态已经改变', val); });
六. 实现 Promise
以下代码非严谨遵守 Promise A+ 规范,仅为demo测试
class MyPromise{ // 1.handleFn:(resolve,reject)=>{} // 2.搭建框架: // ①通过实例化调用的的方法:then... ,promise.then() // ②通过构造函数调用的方法:static resolve... ,Promise.resolve() constructor(handleFn){ // 状态 this.status = 'pending'; // 值 this.value = undefined; // fulfilled 已完成回调数组 this.fulfilledList = []; // rejected 已拒绝回调数组 this.rejectedList = []; // 执行 handleFn,4.triggerResolve,triggerReject handleFn(this.triggerResolve.bind(this), this.triggerReject.bind(this)); } /** * 定义成功方法,作为Promise 传入的函数体的参数 * 实现PromiseA+状态转换 定义成功参数 */ triggerResolve(val){ // 因为需要先把 then 里面注册的回调函数获取到,才能知道在 resolve 里面该去执行哪些回调函数;所以通过 setTimeout(()=>,0)的形式,在下一个Event Loop事件循环内,再去执行回调 // 这里的 triggerResolve 函数,虽然是在构造器内 handleFn 执行了,但是 setTimeout 是在下一个事件循环执行的,所以实际上是先执行的 then ,才是 setTimeout 内的代码 setTimeout(() => { // 在这个里面通过定义 then 方法之后,可以拿到 then 里的回调函数【function () { console.log('resolve')】,从而通过 resolve 的时候,去执行对应的方法 // 如果这里的代码是同步执行的的,不是写在 setTimeout 内,那么此时的状态已经改变了 // 实际执行的时候,还没完全的执行完,所以要判断下 if(this.status !== 'pending') return; // 判断 resolve 方法传入的结果是不是 promise if(val instanceof MyPromise){ // 是promise,则需要给它定义一个 promise, 它的状态就会改变当前所有的状态 val.then( value => {}, err => {} ) }else{ // resolve 方法传入的是普通值 // 把当前 promise 的状态变为【已完成】 this.status = 'fulfilled'; // 同时把值记录下来 this.value = val; // 然后再触发一下,之前在上一个事件循环里面记录的所有回调 this.triggerFulfilled(val); } }, 0); } /** * 定义失败方法,作为Promise 传入的函数体的参数 * 实现PromiseA+状态转换 定义失败参数 */ triggerReject(val){ // 自己写的 setTimeout(() => { if (this.status !== 'pending') return; if(val instanceof MyPromise){ val.then( value => {}, err => {} ) } else{ this.status = 'rejected'; this.value = val; this.triggerRejected(val); } },0); } /** * 将所有已完成状态的回调执行 */ triggerFulfilled(val){ // 拿到 this 上定义的所有已完成的回调,以 forEach 的形式执行一下 this.fulfilledList.forEach(item => item(val)); this.fulfilledList = []; } /** * 将所有已拒绝状态的回调执行 */ triggerRejected(val){ this.rejectedList.forEach(item => item(val)); this.rejectedList = []; } /** * 3.在 then 里面将异步函数【回调函数】注册进去,再在调用 resolve【triggerResolve】 的时候执行了下 * @param onFulfilled 注册的在异步执行的过程中,要去执行的状态改变时的回调函数 * @param onRejected 注册的在异步执行的过程中,要去执行的状态改变时的回调函数 * @returns 每一个then 都要返回一个 Promise */ then(onFulfilled, onRejected){ const {status,value} = this; // 每一个then 都要返回一个 Promise const promiseInstance = new MyPromise((onNextFulfilled, onNextRejected) => { /** * 为了链式调用下去 * @param val triggerFulfilled(val)的时候,去执行的,当前变化的值的时候,改变的这个参数 * @description * ① 为了将 onFulfilled 与 onNextFulfilled 串联起来;当执行 onFulfilled 函数的时候,就知道怎么去执行 onNextRejected * ② 在记录回调函数的过程中,通过内部闭包函数的形式,把【上一个 then 里面注册的回调函数】与【 返回的新 promise 的 resolve函数】进行融合。最终要执行的回调函数的结果 * ③ 在执行 onFinalFulfilled 过程中,可以把当前新创建的 promise【 new MyPromise((onNextFulfilled, onNextRejected)=>{})】的 resolve【onNextFulfilled】函数进行执行 * ④ 当上一个注册的回调函数执行的过程中,我们就可以执行下一个 promise 的结果 */ function onFinalFulfilled(val){ if(typeof onFulfilled !== 'function'){ // 什么时机去执行下一个 promise 的结果? // 规范里写的:如果上一个 promise 返回的结果不是函数,就直接去把上一个 promise 返回的结果放到下一个 promise 里面去执行 onNextFulfilled(val); } else { // 先执行一下 上一步的promise里面的结果,就是res const res = onFulfilled(val); if(res === promiseInstance){ throw new TypeError('不能是同一个promise') } // 判断返回的 是否为 promise if(res instanceof MyPromise){ // 通过 promise.then 的方法,注册 onNextFulfilled 与 onNextRejected 这两个回调 // 怎么去执行下一个 then 里面的结果? // 通过 res 的状态: // ① 如果 res是 resolve 状态,也就是已完成状态,那么就去执行下一个promise的resolve状态【onNextFulfilled】 // ② 如果 res是 reject 状态,也就是已拒绝状态,那么就去执行下一个promise的reject状态【onNextRejected】 res.then(onNextFulfilled, onNextRejected); } else { // 返回一个普通的值,通过调用下一个 promise 的结果 onNextFulfilled(val); } } } function onFinalRejected(error){ if(typeof onRejected !== 'function'){ onNextRejected(error); }else{ let res = null; try { res = onRejected(error); } catch (e) { // 如果在这一步 catch 到了问题,执行下一个 promise链的时候,把这一步暴露的问题传递出去; onNextRejected(e); } // 否则的话,这个还是一个 resolve 的状态 if(res instanceof MyPromise){ res.then(onNextFulfilled, onNextRejected); }else{ onFulfilled(res); } } } // 根据不同的 promise状态,执行不同的方法 switch (status) { case 'pending':{ // 需要记录下当前 注册进来的这两个回调函数 this.fulfilledList.push(onFinalFulfilled); this.rejectedList.push(onFinalRejected); break; } case 'fulfilled': { onFinalFulfilled(value); break; } } }) return promiseInstance; } catch(onRejected){ return this.then(null, onRejected) } /** * MyPromise.resolve 实现 */ static resolve(value){ // 如果返回的值是 promise,则直接将其返回 if(value instanceof MyPromise) return value; // 如果不是,则返回一个 promise 实例,并将它的状态设置为已完成,同时将它的结果value 作为传入 promise 实例的值 return new MyPromise(resolve => resolve(value)); } /** * MyPromise.reject 实现 */ static reject(reason){ if(reason instanceof MyPromise) return reason; return new MyPromise((resolve,reject) => reject(reason)) } /** * MyPromise.all 实现 */ static all(list){ return new MyPromise((resolve,reject) => { if(!Array.isArray(list)) { return reject(new Error('请传入数组')) } let couter = 0 let values = []; // 此处 for of 是并行的;但在 async await 中for of 可能会出现异步迭代,依次执行 // 此处还可以用 forEach,for... for (const [i, MyPromiseInstance] of list.entries()) { MyPromise.resolve(MyPromiseInstance) .then( res => { values[i] = res; couter++; if(couter === list.length) resolve(values); }, err => { reject(err) } ) } }) } /** * MyPromise.race 实现 */ static race(list){ return new MyPromise((resolve,reject) => { if(!Array.isArray(list)) { return reject(new Error('请传入数组')) } list.forEach((item,i) => { // 当某一个promise进入 resolve 状态之后,就进入then MyPromise.resolve(item) .then( res => { resolve(res) }, err => { reject(err) } ) }) }) } } // 实例化过程中 new Promise 执行顺序: // ① 先实例化,执行 handleFunc,再进入 then 里面 ,把回调函数注册进来 // ② 这个时候才会去到 triggerResolve 的 setTimeout()里面去 const promise1 = new MyPromise(function(resolve,reject) { // 注意:进入 then的时候就是 pending状态!!! // 由于对于 此处的 unction(resolve,reject){} 函数来说,一开始会去立刻执行的,但是它的 resolve函数还没有注册进来,所以在注册的时候它还是 pending状态 // 注册完成之后,在下一个事件循环内执行 resolve 里的方法 resolve() // reject() }) promise1.then(function () { console.log('resolve') })
七. 补充
function sleep(time = 1) { return new Promise(resolve => setTimeout(function() {console.log('promise resolve'); resolve()}, time * 1000)) } const promiseCreatorList = [ sleep, sleep, sleep ] /********************************************************************/ // 1. Promise.all 并行执行 console.log('Promise.all start', new Date().getTime()) Promise.all( promiseCreatorList.map(item => item()) ).then( () => console.log('Promise.all end', new Date().getTime()) ) /********************************************************************/ // 2. for of async 迭代依次执行 async function main() { console.log('for of async start', new Date().getTime()) async function forOfLoop() { for (const promiseInstance of promiseCreatorList) { await promiseInstance() } } await forOfLoop() console.log('for of async end', new Date().getTime()) } main() /********************************************************************/ // 3. 把并行的 Promise 实现串联的 const promiseChain = promiseCreatorList.reduce((memo, current)=>{ if(memo.then){ return memo.then(current) } return memo().then(current) }) // 相当于 sleep().then(sleep).then(sleep ) promiseChain.then(function () { console.log('已完成') }) /********************************************************************/ // 4. 通过 Promise 实现依次执行 const promiseChain2 = promiseCreatorList.reduce((memo, current)=>{ return memo().then(current) }, Promise.resolve()) // 相当于 Promise.resolve().then(sleep).then(sleep).then(sleep) promiseChain2.then(function () { console.log('已完成') })
这篇关于Promise 从入?到精通的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-07-04TiDB 资源管控的对撞测试以及最佳实践架构
- 2024-07-03万字长文聊聊Web3的组成架构
- 2024-07-02springboot项目无法注册到nacos-icode9专业技术文章分享
- 2024-06-26结对编程到底难不难?答案在这里
- 2024-06-19《2023版Java工程师》课程升级公告
- 2024-06-15matplotlib作图不显示3D图,怎么办?
- 2024-06-1503-Loki 日志监控
- 2024-06-1504-让LLM理解知识 -Prompt
- 2024-06-05做软件测试需要懂代码吗?
- 2024-06-0514-ShardingSphere的分布式主键实现