QX-AI初始化中...
暂无预设简介,请点击下方生成AI简介按钮。
同步与异步
JS是单线程语言,不能多个线程并发执行,即同一时间,只能处理一个任务
单线程指的是浏览器中只有一个主线程(JS引擎线程)负责解释和执行JS代码,但是浏览器还提供了额外多个副线程用于实现异步非阻塞
1、事件触发线程
2、定时触发器线程
3、异步http请求线程
4、GUI渲染线程
副线程处理完成之后会把对应的回调函数交给消息队列(任务队列)维护,JS引擎线程会在同步任务执行完后,去消息队列中每次取一个任务执行
两种任务
同步任务:在主线程上排队执行任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务:不进入主线程,而是进入消息队列的任务
宏、微任务
每次执行栈中执行的代码就是一个宏任务,在执行宏任务时遇到Promise等,会创建微任务(.then()里面的回调),并加入到微任务队列队尾。
微任务必然是在某个宏任务执行过程时创建的
宏任务来源:script,setTimeout,setInterval,setImmediate,I/O,requestAnimationFrame,UI rendering
微任务来源:process.nextTick,promisesObject.observe,MutationObserver
微任务的执行:在执行栈为空时,会从消息队列中取一个回调函数作为宏任务,在这个宏任务执行前,会执行微任务队列中所有的微任务
事件循环
事件循环就是主线程重复从消息队列中取消息(回调函数)并执行的过程
参考:
一篇搞定(Js异步、事件循环与消息队列、微任务与宏任务)
JS的事件轮询(Event Loop)机制
Promise
Promise(承诺) ES6的新类,是异步编程的新解决方案
作用:把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数
回调地狱:通过回调函数依次执行多个异步操作。Promise通过链式操作解决了回调地狱问题,也能更好地进行错误处理
1 2 3 4 5 6 7 8 9 10
| setTimeout(function () { console.log("等待一秒"); setTimeout(function () { console.log("再等一秒"); setTimeout(function () { console.log("又等一秒"); }, 1000); }, 1000); }, 1000);
|
以fetch为例,它返回一个Promise对象,承诺会在某个时刻返回请求到的数据,请求成功,会调用.then中的回调函数,失败则调用.catch中的
任意一个then出现错误都会触发catch,而then不再往下执行,但catch后的then会继续执行
.then中也可以返回一个Promise,如 res.json() 返回一个Promise,承诺之后将请求的数据返回为json格式,以此实现链式操作
无论是成功还是失败,最后都会执行 .finally 中的回调函数,可以用于提示请求完成、关闭加载动画等
1 2 3 4 5
| fetch('https://api.ooomn.com/api/yan?encode=json') .then(res=> res.json() ) .then(data=> console.log(data) ) .catch(err => console.error(err) ) .finally(()=> console.log('请求结束') )
|
使用
Promise本身是一个构造函数,其对象用来封装一个异步操作,并可以获取其成功或失败的结果值
它拥有all、reject、resolve等方法,原型上有then、catch等方法
有三种状态:pending(进行中)、resolved / fulfilled(已成功)和rejected(已失败),初始状态pending,只能转变为其它状态一次。
状态[[PromiseState]]是Promise实例对象的属性,但并不是一个暴露的属性,无法获取和外部修改
Promise的构造函数接收一个函数参数,传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。
resolve() 会将Promise的状态置为resolved,reject() 会将Promise的状态置为rejected
.then(onResolved, onRejected)
onResolved 和 onRejected 是两个回调函数,分别接收 resolve() 和 reject() 传入的参数,并在对应状态时回调
注意:Promise中所谓的成功或失败,需要根据业务的需要而定义,它只是提供了这两个状态供使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const p = new Promise((resolve, reject)=>{ let num = Math.round(Math.random()); if(num){ resolve(num); }else{ reject(num); } });
p.then( data => { console.log(data) }, data => { console.log(data) } );
|
Promise封装AJAX
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function sendAJAX(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.responseType = 'json'; xhr.open('GET', url, true); xhr.send(); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status <= 300) || xhr.status === 304) { resolve(xhr.response); } else { reject(xhr.status); } } }; }) }
|
使用封装好的 sendAJAX()
1 2 3 4 5 6 7
| sendAJAX('https://api.ooomn.com/api/yan?encode=json') .then( res => console.log(res), reason => console.log(reason) ) .catch( err => console.log(err) ) .finally(()=> console.log('请求完成') )
|
基本流程
属性和方法
1、[[PromiseResult]] 承诺结果,保存着异步任务成功或失败的结果,即保存着 resolve() 或 reject() 传入的值,then中回调函数的参数可获取
2、Promise构造函数传入的函数参数,其内部的操作是同步的
1 2 3 4 5 6
| new Promise((resolve, reject) => { console.log('1'); }) console.log('2');
|
3、then 和 catch
Promise.prototype.then : (onResolved, onRejected) => {}
(1) onResolved 函数: 成功的回调函数 (value) => {}
(2) onRejected 函数: 失败的回调函数 (reason) => {}
说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调
返回一个新的 promise 对象
Promise.prototype.catch : (onRejected) => {}
(1) onRejected 函数: 失败的回调函数 (reason) => {}
说明: then()的语法糖, 相当于: then(undefined, onRejected)
通常不使用then的第二个参数,而是直接使用catch捕获失败
静态方法
1、resolve 和 reject
Promise.resolve : (value) => {}
(1) value: 成功的数据或 promise 对象
说明: 当传入promise对象时,参数的结果决定了promise的结果。将状态从 pending 转为 fulfilled
返回一个成功/失败的 promise 对象
Promise.reject : (reason) => {}
(1) reason: 失败的原因
将状态从 pending 转为 rejected
永远返回一个失败的 promise 对象
1 2 3 4 5 6 7 8 9 10
| let p1 = Promise.resolve(123); p1.then(data => console.log(data));
let p2 = Promise.resolve(new Promise((resolve, reject) => { reject('Error'); })); p2.catch(reason => { console.log(reason); })
|
2、all
Promise.all : (promises) => {}
(1) promises: 包含 n 个 promise 对象的数组
返回一个新的 promise, 所有的 promise 都成功才成功, 只要一个失败就直接失败,多个失败返回第一个失败
1 2 3 4 5 6 7 8 9
| let p1 = new Promise((resolve, reject) => { resolve('OK'); }) let p2 = Promise.resolve('Success'); let p3 = Promise.resolve('Oh Yeah'); const result = Promise.all([p1, p2, p3]) result.then(data=>console.log(data))
|
3、race
Promise.race : (promises) => {}
返回一个新的 promise, 第一个改变状态的 promise 的结果就是返回的结果状态
1 2 3 4 5 6 7 8 9 10 11
| let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('OK'); }, 1000); }) let p2 = Promise.resolve('Success'); let p3 = Promise.resolve('Oh Yeah'); const result = Promise.race([p1, p2, p3]); result.then(data => console.log(data));
|
3、any
Promise.any : (promises) => {}
返回一个新的 promise, 只要有一个成功就成功,全失败才失败,返回第一个成功的结果
1 2 3 4 5 6 7 8 9 10
| let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('OK'); }, 1000); }) let p2 = Promise.reject('reject'); let p3 = Promise.reject('no no no'); const result = Promise.any([p1, p2, p3]); result.then(data => console.log(data));
|
4、allSettled
Promise.allSettled : (promises) => {}
返回一个新的 promise,固定成功不会失败, 所有的 promise 都完成后才完成,返回一个包含所有 promise 结果对象的数组,包含状态、value或reason。
1 2 3 4 5 6 7 8 9 10 11 12
| let p1 = new Promise((resolve, reject) => { reject(1) }) let p2 = Promise.resolve('Success'); let p3 = Promise.resolve('Oh Yeah'); const result = Promise.allSettled([p1, p2, p3]) result.then(data=>console.log(data))
|
更多
1、then的返回值
promise.then()返回一个新 promise 对象
其结果状态如下:
(1) then中抛出异常,返回的 promise 状态为 rejected, reason 为抛出的异常
(2) 返回的是非 promise 的任意值, 状态为 resolved, value 为返回的值
(3) 返回的是另一个 promise, 该 promise 的结果就是返回的 promise 的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const obj = { name: 'chuckle' } const p = new Promise((resolve, reject) => { resolve(obj); }) p.then(data => { console.log(data); data.age = 19; return Promise.resolve(JSON.stringify(data)); }).then(data => { console.log(data); }).then(data => { console.log(data); })
|
2、异常穿透
多个then链式调用,只需要在最后写一个catch,当一个then失败,就不再执行后续的then,直接到catch,catch后面的then会正常执行
1 2 3 4 5 6 7 8 9 10 11 12 13
| const p = Promise.reject('err'); p.then(data => { console.log(1); }).then(data => { console.log(2); }).catch(err => { console.log('err') }).then(data => { console.log(3); })
|
3、中断Promise链
在回调函数中返回一个 pendding 状态的 promise 对象,因为then中回调函数是在 Promise 状态改变时调用的
1 2 3 4 5 6 7 8 9 10 11 12
| const p = Promise.resolve('err'); p.then(data => { console.log(1); }).then(data => { console.log(2); return new Promise(()=>{}) }).then(data => { console.log(3); })
|
async-await
使用Promise对象去实现异步操作,通常会出现一堆异步任务到点了该处理的情况,每次执行的顺序都不一定一样
而async-await允许我们用同步编程的方式去写异步代码,async是基于Promise的语法糖
async函数永远返回一个Promise对象,和then的规则是一样的
await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
1、如果表达式是 promise 对象, await 返回的是 promise 成功的值
2、如果表达式是其它值, 直接将此值作为 await 的返回值
声明一个async函数1 2 3 4 5 6 7 8 9 10 11 12 13
| async function fun(){ console.log('chuckle') return 'qx' } console.log(fun())
fun().then(data=>{ console.log(data); })
|
async函数中的 await 能让js引擎等待其后的代码(通常是返回Promise对象的函数)有结果后再往下执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function a(){ return new Promise((resolve, reject) =>{ setTimeout(() =>{ resolve(2) }, 1000); }) } async function fun(){ console.log(1) let num = await a(); console.log(num) console.log(3) } fun();
|
当多个fetch运行时,结果输出顺序有时并不是代码顺序,有时我们需要它按顺序执行,如果嵌套到then中,反而不美观
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| fetch('https://api.ooomn.com/api/yan?encode=json') .then(res=> res.json() ) .then(data=> { console.log(data) console.log(1) })
fetch('https://api.ooomn.com/api/yan?encode=json') .then(res=> res.json() ) .then(data=> { console.log(data) console.log(2) })
fetch('https://api.ooomn.com/api/yan?encode=json') .then(res=> res.json() ) .then(data=> { console.log(data) console.log(3) })
|
使用 async 优化fetch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| async function fun(){ const url = 'https://api.ooomn.com/api/yan?encode=json'; try{ let res = await Promise.all( [fetch(url), fetch(url), fetch(url)] ); let jsons = res.map(res => res.json()); let values = await Promise.all(jsons) console.log(values); } catch(err){ console.log(err); } } fun();
|
让一个函数等待另一个函数中fetch执行完
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| async function fun(){ const url = 'https://api.ooomn.com/api/yan?encode=json'; try{ let res = await Promise.all( [fetch(url), fetch(url), fetch(url)] ); let jsons = res.map(res => res.json()); let values = await Promise.all(jsons) return values; } catch(err){ console.log(err); } } async function fn(){ let data = 1; data = await fun(); console.log(data); } fn();
|
async-await等待AJAX请求完成,不使用.then
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| function sendAJAX(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.responseType = 'json'; xhr.open('GET', url, true); xhr.send(); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status <= 300) || xhr.status === 304) { resolve(xhr.response); } else { reject(xhr.status); } } }; }) }
btn.addEventListener('click',async function(){ let json = await sendAJAX('https://api.ooomn.com/api/yan?encode=json'); console.log(json); });
|
promisify
Javascript异步演化史,是从callback到Promise再到Async/Await的历程
对于一些老函数,可以使用 nodejs-util、其它第三方库中的 promisify 方法将其转为 Promise 形式的函数
promisify函数作用就是把 callback 形式转成 promise 形式。
只有符合 nodeCallback 的函数才能被 promisify 转化
nodeCallback 有两个条件:1. 回调函数在主函数中的参数位置必须是最后一个;2. 回调函数参数中的第一个参数必须是 error
nodejs中1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const fs = require('fs'); const util = require('util'); const mineReadFile = util.promisify(fs.readFile);
async function main(){ try{ let data1 = await mineReadFile('./1.txt'); let data2 = await mineReadFile('./2.txt'); let data3 = await mineReadFile('./3.txt'); console.log(data1 + data2 + data3); }catch(e){ console.log(e); } } main();
|
简单实现一个promisify
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| var promisify = (func, ctx) => { return function() { var ctx = ctx || this; return new Promise((resolve, reject) => { func.call(ctx, ...arguments, function() { var args = Array.prototype.map.call(arguments, item => item); var err = args.shift(); if (err) { reject(err) } else { args = args.length > 1 ? args : args[0]; resolve(args); } }); }) }; };
var func1 = function(a, b, c, callback) { callback(null, a+b+c); }
var func2 = promisify(func1);
func1(1, 2, 3, (err, result) => { if (!err) { console.log(result); } }) func2(1, 2, 3).then(console.log);
|
输出顺序问题
1、
1 2 3 4 5 6 7 8 9 10 11 12 13
| const p = new Promise((resolve, reject) => { console.log(1); setTimeout(() => { console.log('time'); }, 0); resolve(2); console.log(3); });
p.then((value) => { console.log(value); }); console.log(4);
|
Promise 构造函数的回调是同步的,更改状态后,后续的代码也会执行,then函数的回调是异步的,放到微任务队列中。
2、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(); }, 1000); });
const p2 = p1.catch(() => { return 2; });
console.log('p1:', p1); console.log('p2:', p2);
setTimeout(() => { console.log('p1:', p1); console.log('p2:', p2); }, 2000);
|
1 2 3 4
| p1: Promise { <pending> } p2: Promise { <pending> } p1: Promise { undefined } p2: Promise { undefined }
|
3、
1 2 3 4 5 6 7
| async function fn() { console.log(1); const n = await 2; console.log(n); } fn(); console.log(3);
|
4、
1 2 3 4 5 6 7 8 9 10
| async function fn() { console.log(1); const n = await 2; console.log(n); } (async ()=>{ const n = await fn(); console.log(n); })(); console.log(3);
|
5、
1
| Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log);
|
then 方法只接收函数,若传其它东西,会被忽略,返回上一个promise的结果。
6、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var a = new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 1000); }).then((res) => { console.log(res); return 2; })
var b = new Promise(async (resolve, reject) => { console.log(b); await a; console.log(a); console.log(b); await b; resolve(3); });
console.log('end');
|
1 2 3 4 5
| undefined end 1 Promise { 2 } Promise { <pending> }
|
手写Promise
手写 Promise 即实现 Promise A+ 规范。
当然,A+规范非常详细,想100%实现也非常耗时、困难,这里只是简单实现 ES6-Promise 基本功能。
定义:A+规范并没有设计如何创建、解决和拒绝 Promise,而是专注于提供一个通用的 .then 方法,换而言之,只要一个对象具有 .then 方法,并且符合规范(具有特定参数、返回特定值),就可以称之为 Promise。
Promise A+ 早于 ES6,是社区规范,为了解决回调地狱和异步实现不统一的问题。
ES6 的 Promise 符合 Promise A+ 规范,是对该规范的实现。提供了 Promise 类(构造函数)去构建一个 Promise 对象。并提供了除了 .then 方法之外的一些方法。
状态与数据
先仿照着 ES6-Promise 的样子,构造一个类,完成 resolve
和 reject
的基本功能,改变状态与数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| interface Executor<T = any> { (resolve: Resolve<T>, reject: Reject): void; }
interface Resolve<T = any> { (value?: T): void; }
interface Reject { (reason?: any): void; }
enum State { pending = "pending", fulfilled = "fulfilled", rejected = "rejected", }
class MyPromise<T = any> { private state: State = State.pending; private result?: T;
constructor(executor: Executor<T>) { try { executor(this.resolve, this.reject); } catch(error: any) { this.reject(error); } }
private resolve: Resolve<T> = (value) => { this.changeState(State.fulfilled, value); };
private reject: Reject = (reason) => { this.changeState(State.rejected, reason); };
private changeState(state: State, result?: T) { if (this.state !== State.pending) return; this.state = state; this.result = result; console.log(this.state, this.result); } }
|
微任务
Promise是多平台的通用api,但不同运行环境事件循环并不相同。
在node中使用nextTick近似为微任务,但实际上nextTick任务处于nextTick队列,在微队列前一个执行。
而在浏览器环境,使用 MutationObserver
可以将函数放入微队列中,它用于观察一个DOM节点的变动,当节点变动时会将回调放入微队列中。
最后,使用定时器来作为保底,尽管它属于宏队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| interface Callback { (...argv: any): any; }
export function runMicroTask(callback: Callback) { if (typeof process !== "undefined" && process.nextTick) { process.nextTick(callback); } else if (typeof MutationObserver !== "undefined") { const element = document.createElement("div"); const observer = new MutationObserver(callback); observer.observe(element, { attributes: true, }); element.className = ""; } else { setTimeout(callback, 0); } }
|
then方法
then 方法是 Promise 的核心,本质是用来注册一个成功回调和一个失败回调,在完成后根据状态执行对应回调,并将其放入微队列。
使用一个队列来保存需要执行的 then 回调,且封装为对象,保存一些上下文信息,以判断执行条件。
在状态改变或挂载then时状态已被改变,则清空队列,并执行回调。
在执行then回调时,需注意穿透和返回promise的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
| import { runMicroTask, isPromise, isFunction } from "./utils/index";
interface Executor<T = any> { (resolve: Resolve<T>, reject: Reject): void; }
interface Resolve<T = any> { (value?: T): void; }
interface Reject { (reason?: any): void; }
interface OnFulfilled<T = any> { (value?: T): any; } interface OnRejected { (reason?: any): any; }
interface QueueItem { state: State; fn: OnFulfilled | OnRejected | undefined; resolve: Resolve; reject: Reject; }
enum State { pending = "pending", fulfilled = "fulfilled", rejected = "rejected", }
export default class QXPromise<T = any> { private state: State = State.pending; private result?: T; private queue: Array<QueueItem> = [];
constructor(executor: Executor<T>) { try { executor(this.resolve, this.reject); } catch (error: any) { this.reject(error); } }
private resolve: Resolve<T> = (value) => { this.changeState(State.fulfilled, value); };
private reject: Reject = (reason) => { this.changeState(State.rejected, reason); };
private changeState(state: State, result?: T) { if (this.state !== State.pending) return; this.state = state; this.result = result; this.runQueue(); }
private pushQueue( state: State, fn: OnFulfilled<T> | OnRejected | undefined, resolve: Resolve<T>, reject: Reject ) { this.queue.push({ state, fn, resolve, reject }); }
private runQueue() { if (this.state === State.pending) return; if (this.queue.length <= 0) return; while (this.queue.length) { this.runQueueItem(this.queue[0]); this.queue.shift(); } }
private runQueueItem(item: QueueItem) { if (this.state !== item.state) return; runMicroTask(() => { if (!isFunction(item.fn)) { item.state === State.fulfilled ? item.resolve(this.result) : item.reject(this.result); return; } try { const { fn } = item; const res = fn!(this.result); if (isPromise(res)) { res.then(item.resolve, item.reject); return; } item.resolve(res); } catch (error: any) { item.reject(error); } }); }
public then( onFulfilled?: OnFulfilled<T>, onRejected?: OnRejected ): QXPromise { return new QXPromise((resolve, reject) => { this.pushQueue(State.fulfilled, onFulfilled, resolve, reject); this.pushQueue(State.rejected, onRejected, resolve, reject); this.runQueue(); }); } }
|
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| const p1 = new QXPromise((resolve, reject) => { setTimeout(() => { resolve(2); }, 1000); });
p1.then( function (this: any, res) { console.log(this, res); return new QXPromise((resolve, reject) => { setTimeout(() => { resolve("a"); }, 500); }); }, (err) => { console.log(err); } ).then( (res) => { console.log(res); }, (err) => { console.log(err); } );
setTimeout(() => { p1.then((res) => { throw 123; }).then( (res) => { console.log(res); }, (err) => { console.log("err:", err); } ); }, 2000);
const p2 = new QXPromise((resolve, reject) => { setTimeout(() => { resolve(2); }, 2000); });
p2.then((res) => { console.log(res); return Promise.resolve("b"); }) .then((res) => { console.log(res); return new QXPromise((resolve, reject) => { setTimeout(() => { resolve("c"); }, 500); }); }) .then((res) => { console.log(res); return Promise.reject("d"); }).then(()=>{},(err)=>{ console.log(err); });
function delay(duration: number) { return new QXPromise((resolve) => { setTimeout(resolve, duration); }); } (async ()=>{ console.log("start"); await delay(3000); console.log("end"); })()
|
现在 Promise A+ 规范以及基本实现完了,至于其它方法,如catch、all等,是 ES6 特有的方法,下面接着实现。
catch
catch 方法本质就是仅传入第二个回调的 then 方法。
1 2 3 4
| public catch(onRejected: OnRejected): QXPromise { return this.then(undefined, onRejected); }
|
测试:
1 2 3 4 5 6 7 8
| const p3 = new QXPromise((resolve, reject) => { setTimeout(() => { reject("error!!!"); }, 500); }).catch((err) => { console.log(err); });
|
finally
finally 像是一个中间商,它无论如何都会执行。
且虽然叫做 finally 但其返回的也是 Promise,且忽略返回值进行穿透。但若出现错误,则为 reject。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public finally(onFinally: () => void): QXPromise { return this.then( (value) => { onFinally(); return value; }, (reason) => { onFinally(); throw reason; } ); }
|
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const p4 = new QXPromise((resolve, reject) => { setTimeout(() => { resolve("ok"); }, 500); }).finally(() => { console.log("finally"); }).finally(() => { console.log("finally"); }).then((res) => { console.log("after finally:", res); }).finally(() => { console.log("finally"); throw "finally error"; }).catch((err) => { console.log("error:", err); });
|
resolve和reject
这两个静态方法本质也是一次简单封装,用于快速返回一个已完成的promise。
resolve规则:
- 传入本身就是ES6-Promise,直接返回这个Promise
- 传入PromiseLike,也就是实现了A+规范,返回新的Promise,状态保持一致
- 其余情况返回新的Promise,resolve传入的值
reject规则:
- 直接reject传入的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| static resolve<T>(value: any): QXPromise<T> | Promise<T> { if (value instanceof Promise) return value; return new QXPromise((resolve, reject) => { if (isPromise(value)) { value.then(resolve, reject); } else { resolve(value); } }); }
static reject(reason: any): QXPromise { return new QXPromise((_, reject) => { reject(reason); }); }
|
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const p5 = QXPromise.resolve("resolve"); p5.then((res) => { console.log(res); }); QXPromise.resolve(Promise.resolve("es6 resolve")).then((res) => { console.log(res); }); QXPromise.resolve(Promise.reject("reject es6 resolve")).catch((res) => { console.log(res); }); QXPromise.resolve(QXPromise.reject("my reject")).catch((res) => { console.log(res); }); const p6 = QXPromise.reject("reject"); p6.catch((err) => { console.log(err); });
|
all
all
静态方法传入一个 Promise 数组或迭代器,返回一个新 Promise,仅当所有传入的 Promise 成功才成功,一个失败则返回首个失败的原因。
数组中传入其它值,会使用 Promise.resolve()
转为 Promise。传入非迭代器对象直接 reject。传入空数组直接返回成功,结果为空数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| static all<T>(promises: Iterable<T | PromiseLike<T>>) { return new QXPromise<T[]>((resolve, reject) => { try { const result: T[] = []; let count = 0; let fulfilledCount = 0; for (const p of promises) { let index = count; this.resolve(p).then((data) => { result[index] = data; fulfilledCount++; if (fulfilledCount === count) { resolve(result); } }, reject); count++; } if (count === 0) { resolve([]); } } catch (error) { reject(error); } }); }
|
测试:
1 2 3 4 5 6 7 8 9 10 11 12
| const pp = new QXPromise((resolve, reject) => { setTimeout(() => { resolve(0); }, 1000); }); QXPromise.all([pp, 1, 2, 3]) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); });
|
allSettled
allSettled
方法,和 all 差不多,但不管成功失败都会成功,且结果为数组,中包含每个 Promise 的最终状态、成功结果或失败原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| interface AllSettledResult<T> { status: State.fulfilled | State.rejected; value?: T; reason?: any; } type AllSettledResolveResult<T> = Required< Pick<AllSettledResult<T>, "status" | "value"> >; type AllSettledRejectResult = Required< Pick<AllSettledResult<never>, "status" | "reason"> >;
static allSettled<T>(promises: Iterable<T | PromiseLike<T>>) { const promiseList: Array<any> = []; for (const p of promises) { promiseList.push( this.resolve(p).then( (value): AllSettledResolveResult<T> => { return { status: State.fulfilled, value }; }, (reason): AllSettledRejectResult => { return { status: State.rejected, reason }; } ) ); } return this.all<AllSettledResult<T>>(promiseList); }
|
测试:
1 2 3 4 5 6 7 8
| QXPromise.allSettled([1, 2, QXPromise.reject(3)]).then((res) => { res.forEach((item) => { console.log(item); }); });
|
race
race
就像竞速,第一个改变状态的 promise 的结果就是返回的结果状态,也即是新的 Promise 的成功与否取决于第一个完成的 Promise
当传入空数组时,返回的 Promise 将永远 pending。
1 2 3 4 5 6 7 8
| static race<T>(promises: Iterable<T | QXPromise<T> | PromiseLike<T>>) { return new QXPromise<T>((resolve, reject) => { for (const p of promises) { this.resolve(p).then(resolve, reject); } }); }
|
测试:
1 2 3 4 5 6 7
| QXPromise.race([QXPromise.reject(1), QXPromise.resolve(2)]) .then((res) => { console.log(res); }) .catch((err) => { console.log("err", err); });
|
完整代码
https://github.com/qxchuckle/qx-promise
src\index.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
| import { runMicroTask, isPromise, isFunction } from "./utils/index";
interface Executor<T = any> { (resolve: Resolve<T>, reject: Reject): void; }
interface Resolve<T = any> { (value?: T): void; }
interface Reject { (reason?: any): void; }
interface OnFulfilled<T, R = T> { (value: T): R | QXPromise<R> | null | undefined; } interface OnRejected<T = any, R = T> { (reason: T): R | QXPromise<R> | null | undefined; }
interface QueueItem<T> { state: State; fn: OnFulfilled<T> | OnRejected | undefined | null; resolve: Resolve; reject: Reject; }
interface AllSettledResult<T> { status: State.fulfilled | State.rejected; value?: T; reason?: any; } type AllSettledResolveResult<T> = Required< Pick<AllSettledResult<T>, "status" | "value"> >; type AllSettledRejectResult = Required< Pick<AllSettledResult<never>, "status" | "reason"> >;
enum State { pending = "pending", fulfilled = "fulfilled", rejected = "rejected", }
export default class QXPromise<T = any> { private state: State = State.pending; private result?: T; private queue: Array<QueueItem<T>> = [];
constructor(executor: Executor<T>) { try { executor(this.resolve, this.reject); } catch (error: any) { this.reject(error); } }
private resolve: Resolve<T> = (value) => { this.changeState(State.fulfilled, value); };
private reject: Reject = (reason) => { this.changeState(State.rejected, reason); };
private changeState(state: State, result?: T) { if (this.state !== State.pending) return; this.state = state; this.result = result; this.runQueue(); }
private pushQueue( state: State, fn: OnFulfilled<T> | OnRejected | undefined | null, resolve: Resolve, reject: Reject ) { this.queue.push({ state, fn, resolve, reject }); }
private runQueue() { if (this.state === State.pending) return; if (this.queue.length <= 0) return; while (this.queue.length) { this.runQueueItem(this.queue[0]); this.queue.shift(); } }
private runQueueItem(item: QueueItem<T>) { if (this.state !== item.state) return; runMicroTask(() => { if (!isFunction(item.fn)) { item.state === State.fulfilled ? item.resolve(this.result) : item.reject(this.result); return; } try { const { fn } = item; const res = fn!(this.result!); if (isPromise(res)) { res.then(item.resolve, item.reject); return; } item.resolve(res); } catch (error: any) { item.reject(error); } }); }
public then<R1 = T, R2 = never>( onFulfilled?: OnFulfilled<T, R1>, onRejected?: OnRejected<T, R2> ) { return new QXPromise<R1 | R2>((resolve, reject) => { this.pushQueue(State.fulfilled, onFulfilled, resolve, reject); this.pushQueue(State.rejected, onRejected, resolve, reject); this.runQueue(); }); }
public catch(onRejected: OnRejected): QXPromise { return this.then(undefined, onRejected); }
public finally(onFinally: () => void): QXPromise { return this.then( (value) => { onFinally(); return value; }, (reason) => { onFinally(); throw reason; } ); }
static resolve<T>(value?: T | QXPromise<T> | PromiseLike<T>): QXPromise<T>{ if (value instanceof Promise) return value as QXPromise<T>; return new QXPromise((resolve, reject) => { if (isPromise(value)) { (value as QXPromise<T>).then(resolve, reject); } else { resolve(value as T); } }); }
static reject(reason?: any): QXPromise { return new QXPromise((_, reject) => { reject(reason); }); }
static all<T>(promises: Iterable<T | QXPromise<T> | PromiseLike<T>>) { return new QXPromise<T[]>((resolve, reject) => { try { const result: T[] = []; let count = 0; let fulfilledCount = 0; for (const p of promises) { let index = count; this.resolve(p).then((data) => { result[index] = data; fulfilledCount++; if (fulfilledCount === count) { resolve(result); } }, reject); count++; } if (count === 0) { resolve([]); } } catch (error) { reject(error); } }); }
static allSettled<T>(promises: Iterable<T | QXPromise<T> | PromiseLike<T>>) { const promiseList: Array<any> = []; for (const p of promises) { promiseList.push( this.resolve(p).then( (value): AllSettledResolveResult<T> => { return { status: State.fulfilled, value }; }, (reason): AllSettledRejectResult => { return { status: State.rejected, reason }; } ) ); } return this.all<AllSettledResult<T>>(promiseList); }
static race<T>(promises: Iterable<T | QXPromise<T> | PromiseLike<T>>) { return new QXPromise<T>((resolve, reject) => { for (const p of promises) { this.resolve(p).then(resolve, reject); } }); }
get [Symbol.toStringTag]() { return this.constructor.name; } }
|
src\utils\index.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| interface Callback { (...argv: any): any; }
export function runMicroTask(callback: Callback) { if (typeof process !== "undefined" && process.nextTick) { process.nextTick(callback); } else if (typeof MutationObserver !== "undefined") { const element = document.createElement("div"); const observer = new MutationObserver(callback); observer.observe(element, { attributes: true, }); element.className = ""; } else { setTimeout(callback, 0); } }
export function isObject(val: any) { return val !== null && (typeof val === "object" || typeof val === "function"); }
export function isPromise(p: any) { return p instanceof Promise || (isObject(p) && typeof p.then === "function"); }
export function isFunction(val: any) { return typeof val === "function"; }
|