轻笑Chuckle
外观

学习笔记

Promise异步编程

事件循环、Promise、async-await、promisify、手写Promise

13 分钟 5,290 字 次浏览 条评论 更新于 2024年3月21日

同步与异步

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通过链式操作解决了回调地狱问题,也能更好地进行错误处理

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 中的回调函数,可以用于提示请求完成、关闭加载动画等

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中所谓的成功或失败,需要根据业务的需要而定义,它只是提供了这两个状态供使用。

const p = new Promise((resolve, reject)=>{
// 随机生成0或1
let num = Math.round(Math.random());
// 定义num为1时是成功,0为失败,调用resolve()
if(num){
resolve(num);
}else{
reject(num);
}
});
p.then(
data => { console.log(data) },
data => { console.log(data) }
);

Promise封装AJAX

function sendAJAX(url) {
// 返回一个Promise对象
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()

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构造函数传入的函数参数,其内部的操作是同步的

new Promise((resolve, reject) => {
console.log('1');
})
console.log('2');
// 1
// 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 对象

let p1 = Promise.resolve(123);
p1.then(data => console.log(data));// 123
let p2 = Promise.resolve(new Promise((resolve, reject) => {
reject('Error');
}));
p2.catch(reason => {
console.log(reason);// Error
})

2、all Promise.all : (promises) => {} (1) promises: 包含 n 个 promise 对象的数组 返回一个新的 promise, 所有的 promise 都成功才成功, 只要一个失败就直接失败,多个失败返回第一个失败

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) ['OK', 'Success', 'Oh Yeah']

3、race Promise.race : (promises) => {} 返回一个新的 promise, 第一个改变状态的 promise 的结果就是返回的结果状态

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));
// Success

3、any Promise.any : (promises) => {} 返回一个新的 promise, 只要有一个成功就成功,全失败才失败,返回第一个成功的结果

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));
// OK

4、allSettled Promise.allSettled : (promises) => {} 返回一个新的 promise,固定成功不会失败, 所有的 promise 都完成后才完成,返回一个包含所有 promise 结果对象的数组,包含状态、value或reason。

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))
// [
// {status: 'rejected', reason: 1},
// {status: 'fulfilled', value: 'Success'},
// {status: 'fulfilled', value: 'Oh Yeah'},
// ]

更多

1、then的返回值 promise.then()返回一个新 promise 对象 其结果状态如下: (1) then中抛出异常,返回的 promise 状态为 rejected, reason 为抛出的异常 (2) 返回的是非 promise 的任意值, 状态为 resolved, value 为返回的值 (3) 返回的是另一个 promise, 该 promise 的结果就是返回的 promise 的结果

const obj = { name: 'chuckle' }
const p = new Promise((resolve, reject) => {
resolve(obj);
})
p.then(data => {
console.log(data);// {name: 'chuckle'}
data.age = 19;
return Promise.resolve(JSON.stringify(data));
}).then(data => {
console.log(data);// {"name":"chuckle","age":19}
}).then(data => {
console.log(data);// undefined
})

2、异常穿透 多个then链式调用,只需要在最后写一个catch,当一个then失败,就不再执行后续的then,直接到catch,catch后面的then会正常执行

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);
})
// err
// 3

3、中断Promise链 在回调函数中返回一个 pendding 状态的 promise 对象,因为then中回调函数是在 Promise 状态改变时调用的

const p = Promise.resolve('err');
p.then(data => {
console.log(1);
}).then(data => {
console.log(2);
return new Promise(()=>{})
}).then(data => {
console.log(3);
})
// 1
// 2

async-await

使用Promise对象去实现异步操作,通常会出现一堆异步任务到点了该处理的情况,每次执行的顺序都不一定一样 而async-await允许我们用同步编程的方式去写异步代码,async是基于Promise的语法糖

async函数永远返回一个Promise对象,和then的规则是一样的

await 右侧的表达式一般为 promise 对象, 但也可以是其它的值 1、如果表达式是 promise 对象, await 返回的是 promise 成功的值 2、如果表达式是其它值, 直接将此值作为 await 的返回值

async function fun(){
console.log('chuckle')
return 'qx'// 返回什么,返回的Promise对象的结果就是什么
}
console.log(fun())
//Promise {<fulfilled>: 'qx'}
// [[Prototype]]: Promise
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: "qx"
fun().then(data=>{
console.log(data);// qx
})

async函数中的 await 能让js引擎等待其后的代码(通常是返回Promise对象的函数)有结果后再往下执行

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();
// 1
// 等待一秒后
// 2
// 3

当多个fetch运行时,结果输出顺序有时并不是代码顺序,有时我们需要它按顺序执行,如果嵌套到then中,反而不美观

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

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());
// json()返回的是promise对象,还需要再进行一次Promise.all
let values = await Promise.all(jsons)
console.log(values);
} catch(err){
console.log(err);
}
}
fun();

让一个函数等待另一个函数中fetch执行完

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());
// json()返回的是promise对象,还需要再进行一次Promise.all
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();// (3) [{…}, {…}, {…}]

async-await等待AJAX请求完成,不使用.then

// 之前封装的Promise形式AJAX
function sendAJAX(url) {
// 返回一个Promise对象
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

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

var promisify = (func, ctx) => {
// 返回一个新的function
return function() {
// 初始化this作用域
var ctx = ctx || this;
// 新方法返回的promise
return new Promise((resolve, reject) => {
// 调用原来的非promise方法func,绑定作用域,传参,以及callback(callback为func的最后一个参数)
func.call(ctx, ...arguments, function() {
// 将回调函数中的的第一个参数error单独取出
var args = Array.prototype.map.call(arguments, item => item);
var err = args.shift();
// 判断是否有error
if (err) {
reject(err)
} else {
// 没有error则将后续参数resolve出来
args = args.length > 1 ? args : args[0];
resolve(args);
}
});
})
};
};
// nodeCallback方法func1
var func1 = function(a, b, c, callback) {
callback(null, a+b+c);
}
// promise化后的func2
var func2 = promisify(func1);
// 调用后输出6
func1(1, 2, 3, (err, result) => {
if (!err) {
console.log(result); //输出6
}
})
func2(1, 2, 3).then(console.log); //输出6

输出顺序问题

1、

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);
1
3
4
2
time

Promise 构造函数的回调是同步的,更改状态后,后续的代码也会执行,then函数的回调是异步的,放到微任务队列中。

2、

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);
p1: Promise { <pending> }
p2: Promise { <pending> }
p1: Promise { undefined }
p2: Promise { undefined }

3、

async function fn() {
console.log(1);
const n = await 2;
console.log(n);
}
fn();
console.log(3);
1
3
2

4、

async function fn() {
console.log(1);
const n = await 2;
console.log(n);
}
(async ()=>{
const n = await fn();
console.log(n);
})();
console.log(3);
1
3
2
undefined

5、

Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log);
1

then 方法只接收函数,若传其它东西,会被忽略,返回上一个promise的结果。

6、

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');
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 的基本功能,改变状态与数据。

// 执行器
interface Executor<T = any> {
(resolve: Resolve<T>, reject: Reject): void;
}
// resolve
interface Resolve<T = any> {
(value?: T): void;
}
// reject
interface Reject {
(reason?: any): void;
}
// 状态枚举
enum State {
pending = "pending",
fulfilled = "fulfilled",
rejected = "rejected",
}
// 手写Promise
class MyPromise<T = any> {
// 状态
private state: State = State.pending;
// 结果
private result?: T;
constructor(executor: Executor<T>) {
// 捕获执行器抛出的错误,改为reject状态
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) {
// 只允许从pending状态改变一次
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节点的变动,当节点变动时会将回调放入微队列中。

最后,使用定时器来作为保底,尽管它属于宏队列。

interface Callback {
(...argv: any): any;
}
// 将回调放入微任务队列
export function runMicroTask(callback: Callback) {
// 判断是否为node环境
if (typeof process !== "undefined" && process.nextTick) {
// node环境使用nextTick模拟微任务
// 实际上nextTick任务处于nextTick队列,在微队列前一个执行
process.nextTick(callback);
} else if (typeof MutationObserver !== "undefined") {
// 浏览器环境使用MutationObserver将函数放入微队列
// MutationObserver用于观察一个DOM节点的变动,当节点变动时会将回调放入微队列中。
const element = document.createElement("div");
const observer = new MutationObserver(callback);
observer.observe(element, {
attributes: true, // 观察属性变动
});
element.className = "";
} else {
// 保底使用setTimeout
setTimeout(callback, 0);
}
}

then方法

then 方法是 Promise 的核心,本质是用来注册一个成功回调和一个失败回调,在完成后根据状态执行对应回调,并将其放入微队列。

使用一个队列来保存需要执行的 then 回调,且封装为对象,保存一些上下文信息,以判断执行条件。

在状态改变或挂载then时状态已被改变,则清空队列,并执行回调。

在执行then回调时,需注意穿透和返回promise的情况。

import { runMicroTask, isPromise, isFunction } from "./utils/index";
// 执行器
interface Executor<T = any> {
(resolve: Resolve<T>, reject: Reject): void;
}
// resolve
interface Resolve<T = any> {
(value?: T): void;
}
// reject
interface Reject {
(reason?: any): void;
}
// A+规范then方法参数
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",
}
// 手写Promise
export default class QXPromise<T = any> {
// 状态
private state: State = State.pending;
// 结果
private result?: T;
// 队列
private queue: Array<QueueItem> = [];
constructor(executor: Executor<T>) {
// 捕获执行器抛出的错误,改为reject状态
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) {
// 只允许从pending状态改变一次
if (this.state !== State.pending) return;
this.state = state;
this.result = result;
// console.log(this);
// 状态改变时执行队列
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;
// console.log(this.queue);
// 队列尾进头出
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 {
// 解构出函数,使this指向不改变。
const { fn } = item;
const res = fn!(this.result);
// 判断返回值是否为Promise
if (isPromise(res)) {
// 等待Promise状态改变,再执行完成或失败
res.then(item.resolve, item.reject);
return;
}
item.resolve(res);
} catch (error: any) {
item.reject(error);
}
});
}
// then方法
public then(
onFulfilled?: OnFulfilled<T>,
onRejected?: OnRejected
): QXPromise {
return new QXPromise((resolve, reject) => {
// 将then方法的回调放入队列,未来执行
// 现在不用考虑fn是否是可执行函数,因为在执行队列时会判断。不是函数会进行穿透。
this.pushQueue(State.fulfilled, onFulfilled, resolve, reject);
this.pushQueue(State.rejected, onRejected, resolve, reject);
// 若状态已改变,立即执行队列
// 因为then可能在状态改变以后又被调用。
this.runQueue();
});
}
}

测试:

/* 测试 */
const p1 = new QXPromise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 1000);
});
p1.then(
function (this: any, res) {
console.log(this, res);
// 测试返回Promise
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);
// 与 ES6-Promise 互操作
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);
});
// 兼容await
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 方法。

// catch方法,本质就是then方法
public catch(onRejected: OnRejected): QXPromise {
return this.then(undefined, onRejected);
}

测试:

// 测试catch
const p3 = new QXPromise((resolve, reject) => {
setTimeout(() => {
reject("error!!!");
}, 500);
}).catch((err) => {
console.log(err);
});

finally

finally 像是一个中间商,它无论如何都会执行。

且虽然叫做 finally 但其返回的也是 Promise,且忽略返回值进行穿透。但若出现错误,则为 reject。

// finally方法,本质也是then方法
public finally(onFinally: () => void): QXPromise {
// 无论状态如何,都会执行finally回调
return this.then(
(value) => {
onFinally();
// 返回值不变,继续传递resolve状态
return value;
},
(reason) => {
onFinally();
// 抛出错误,继续传递reject状态
throw reason;
}
);
}

测试:

// 测试finally
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规则:

  1. 传入本身就是ES6-Promise,直接返回这个Promise
  2. 传入PromiseLike,也就是实现了A+规范,返回新的Promise,状态保持一致
  3. 其余情况返回新的Promise,resolve传入的值

reject规则:

  1. 直接reject传入的值。
// 静态resolve方法
static resolve<T>(value: any): QXPromise<T> | Promise<T> {
// 如果是ES6-Promise实例,直接返回
if (value instanceof Promise) return value;
return new QXPromise((resolve, reject) => {
if (isPromise(value)) {
// 如果是PromiseLike,等待状态改变
value.then(resolve, reject);
} else {
// 直接resolve值
resolve(value);
}
});
}
// 静态reject方法
static reject(reason: any): QXPromise {
return new QXPromise((_, reject) => {
reject(reason);
});
}

测试:

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。传入空数组直接返回成功,结果为空数组。

static all<T>(promises: Iterable<T | PromiseLike<T>>) {
return new QXPromise<T[]>((resolve, reject) => {
try {
const result: T[] = [];
let count = 0; // 总数计数
let fulfilledCount = 0; // Promise完成计数
for (const p of promises) {
let index = count; // 记录当前Promise的索引
this.resolve(p).then((data) => {
// 将结果放入对应索引的位置
result[index] = data;
// 完成计数+1
fulfilledCount++;
// 所有Promise完成后,resolve结果
if (fulfilledCount === count) {
resolve(result);
}
}, reject);
count++;
}
if (count === 0) {
// 如果传入空数组,直接resolve空数组
resolve([]);
}
} catch (error) {
// 捕获错误,直接reject
reject(error);
}
});
}

测试:

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 的最终状态、成功结果或失败原因。

// allSettled返回结果类型
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>>) {
// 保存加工后的Promises
const promiseList: Array<any> = [];
// 对每个Promise进行加工
for (const p of promises) {
promiseList.push(
// 让每个Promise都变成始终成功返回Result的Promise
this.resolve(p).then(
(value): AllSettledResolveResult<T> => {
return { status: State.fulfilled, value };
},
(reason): AllSettledRejectResult => {
return { status: State.rejected, reason };
}
)
);
}
// 使用all方法,等待所有Promise完成,返回Result类型成功结果
return this.all<AllSettledResult<T>>(promiseList);
}

测试:

QXPromise.allSettled([1, 2, QXPromise.reject(3)]).then((res) => {
res.forEach((item) => {
console.log(item);
});
});
// { status: 'fulfilled', value: 1 }
// { status: 'fulfilled', value: 2 }
// { status: 'rejected', reason: 3 }

race

race 就像竞速,第一个改变状态的 promise 的结果就是返回的结果状态,也即是新的 Promise 的成功与否取决于第一个完成的 Promise

当传入空数组时,返回的 Promise 将永远 pending。

static race<T>(promises: Iterable<T | QXPromise<T> | PromiseLike<T>>) {
return new QXPromise<T>((resolve, reject) => {
for (const p of promises) {
// 只要有一个Promise状态改变,就立即改变
this.resolve(p).then(resolve, reject);
}
});
}

测试:

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

import { runMicroTask, isPromise, isFunction } from "./utils/index";
// 执行器
interface Executor<T = any> {
(resolve: Resolve<T>, reject: Reject): void;
}
// resolve
interface Resolve<T = any> {
(value?: T): void;
}
// reject
interface Reject {
(reason?: any): void;
}
// A+规范then方法参数
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;
}
// allSettled返回结果类型
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",
}
// 手写Promise
export default class QXPromise<T = any> {
// 状态
private state: State = State.pending;
// 结果
private result?: T;
// 队列
private queue: Array<QueueItem<T>> = [];
constructor(executor: Executor<T>) {
// 捕获执行器抛出的错误,改为reject状态
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) {
// 只允许从pending状态改变一次
if (this.state !== State.pending) return;
this.state = state;
this.result = result;
// console.log(this);
// 状态改变时执行队列
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;
// console.log(this.queue);
// 队列尾进头出
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 {
// 解构出函数,使this指向不改变。
const { fn } = item;
const res = fn!(this.result!);
// 判断返回值是否为Promise
if (isPromise(res)) {
// 等待Promise状态改变,再执行完成或失败
res.then(item.resolve, item.reject);
return;
}
item.resolve(res);
} catch (error: any) {
// console.log(error);
item.reject(error);
}
});
}
// then方法
public then<R1 = T, R2 = never>(
onFulfilled?: OnFulfilled<T, R1>,
onRejected?: OnRejected<T, R2>
) {
return new QXPromise<R1 | R2>((resolve, reject) => {
// 将then方法的回调放入队列,未来执行
// 现在不用考虑fn是否是可执行函数,因为在执行队列时会判断。不是函数会进行穿透。
this.pushQueue(State.fulfilled, onFulfilled, resolve, reject);
this.pushQueue(State.rejected, onRejected, resolve, reject);
// 若状态已改变,立即执行队列
// 因为then可能在状态改变以后又被调用。
this.runQueue();
});
}
// catch方法,本质就是then方法
public catch(onRejected: OnRejected): QXPromise {
return this.then(undefined, onRejected);
}
// finally方法,本质也是then方法
public finally(onFinally: () => void): QXPromise {
// 无论状态如何,都会执行finally回调
return this.then(
(value) => {
onFinally();
// 返回值不变,继续传递resolve状态
return value;
},
(reason) => {
onFinally();
// 抛出错误,继续传递reject状态
throw reason;
}
);
}
// 静态resolve方法
static resolve<T>(value?: T | QXPromise<T> | PromiseLike<T>): QXPromise<T>{
// 如果是ES6-Promise实例,直接返回
if (value instanceof Promise) return value as QXPromise<T>;
return new QXPromise((resolve, reject) => {
if (isPromise(value)) {
// 如果是PromiseLike,等待状态改变
(value as QXPromise<T>).then(resolve, reject);
} else {
// 直接resolve值
resolve(value as T);
}
});
}
// 静态reject方法
static reject(reason?: any): QXPromise {
return new QXPromise((_, reject) => {
reject(reason);
});
}
// 静态all方法
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; // Promise完成计数
for (const p of promises) {
let index = count; // 记录当前Promise的索引
this.resolve(p).then((data) => {
// 将结果放入对应索引的位置
result[index] = data;
// 完成计数+1
fulfilledCount++;
// 所有Promise完成后,resolve结果
if (fulfilledCount === count) {
resolve(result);
}
}, reject);
count++;
}
if (count === 0) {
// 如果传入空数组,直接resolve空数组
resolve([]);
}
} catch (error) {
// 捕获错误,直接reject
reject(error);
}
});
}
// 静态allSettled方法
static allSettled<T>(promises: Iterable<T | QXPromise<T> | PromiseLike<T>>) {
// 保存加工后的Promises
const promiseList: Array<any> = [];
// 对每个Promise进行加工
for (const p of promises) {
promiseList.push(
// 让每个Promise都变成始终成功返回Result的Promise
this.resolve(p).then(
(value): AllSettledResolveResult<T> => {
return { status: State.fulfilled, value };
},
(reason): AllSettledRejectResult => {
return { status: State.rejected, reason };
}
)
);
}
// 使用all方法,等待所有Promise完成,返回Result类型成功结果
return this.all<AllSettledResult<T>>(promiseList);
}
// 静态race方法
static race<T>(promises: Iterable<T | QXPromise<T> | PromiseLike<T>>) {
return new QXPromise<T>((resolve, reject) => {
for (const p of promises) {
// 只要有一个Promise状态改变,就立即改变
this.resolve(p).then(resolve, reject);
}
});
}
get [Symbol.toStringTag]() {
return this.constructor.name;
}
}
interface Callback {
(...argv: any): any;
}
// 将回调放入微任务队列
export function runMicroTask(callback: Callback) {
// 判断是否为node环境
if (typeof process !== "undefined" && process.nextTick) {
// node环境使用nextTick模拟微任务
// 实际上nextTick任务处于nextTick队列,在微队列前一个执行
process.nextTick(callback);
} else if (typeof MutationObserver !== "undefined") {
// 浏览器环境使用MutationObserver将函数放入微队列
// MutationObserver用于观察一个DOM节点的变动,当节点变动时会将回调放入微队列中。
const element = document.createElement("div");
const observer = new MutationObserver(callback);
observer.observe(element, {
attributes: true, // 观察属性变动
});
element.className = "";
} else {
// 保底使用setTimeout
setTimeout(callback, 0);
}
}
// 判断是否为对象
export function isObject(val: any) {
return val !== null && (typeof val === "object" || typeof val === "function");
}
// 判断是否为Promise
export function isPromise(p: any) {
return p instanceof Promise || (isObject(p) && typeof p.then === "function");
}
// 判断是否为函数
export function isFunction(val: any) {
return typeof val === "function";
}

评论

快速前往