异步编程简介- promise

异步编程简介- promise

异步编程历史
前端异步编程经历callback、promise、generate、async/await几个阶段。
目前在简单场景使用回调,步骤比较多的场景使用promise和async/await,generate昙花一现,由于其api不易理解并且不易于使用而很少使用。

Promise

简介

promise目的:异步编程解决回调地狱,让程序开发者编写的异步代码具有更好的可读性。
promise规范规定了一种异步编程解决方案的API。规范规定了promise对象的状态和then方法。
promise是这种异步编程的解决方案的具体实现。
状态特性用来让使用promise的用户可以及时通知promise任务执行结果。
then特性让使用promise的用户可以控制执行完一个任务后执行下一个任务。
(使用回调进行异步编程的话,都是用户手动控制的,使用promise的话,只需要告诉promise:“我要执行什么任务”、“我执行的任务结束了”、“然后我要做什么”)

promise语法

promise对象

new Promise对象时候传入函数,函数立即执行,函数接收resolve、reject参数,调用resolve或reject时候会改变promise状态。状态改变后不会再变化。
promise状态
pending
fullfilled
rejected
未调用resolve或者reject时候处于pending状态,调用resolve后处于fullfilled状态,调用reject后处于rejected状态。如果在pending状态时候,执行任务抛出错误,则变成reject状态。
状态变化后,会执行通过then注册的回调。执行顺序和调用then方法的顺序相同。
调用then方法时候,如果状态是pending则注册回调,等到状态改变时候执行,如果状态已经改变则执行相应的回调。

const p = new Promise((resolve, reject) => {
	resolve('test');
});

p.then(
	data => console.log(1, 'resolve', data),
  data => console.log(1, 'reject', data)
);

p.then(
	data => console.log(2, 'resolve', data),
  data => console.log(2, 'reject', data)
);

// 执行结果
1 "resolve" "test"
2 "resolve" "test"
const p = new Promise((resolve, reject) => {
  throw new Error('test-error');
  // 由于抛出错误,promise状态已经改变为rejected,再调用resolve将不会改变promise状态
	resolve('test');
});

p.then(
	data => console.log(1, 'resolve', data),
  data => console.log(1, 'reject', data)
);

p.then(
	data => console.log(2, 'resolve', data),
  data => console.log(2, 'reject', data)
);

// 执行结果
1 "reject" Error: test-error
2 "reject" Error: test-error
const p = new Promise((resolve, reject) => {
	setTimeout(() => {
  	resolve('test');
  }, 1000);
});

p.then(
	data => console.log(1, 'resolve', data),
  data => console.log(1, 'reject', data)
);

// 执行结果
1s后打印 `1 "resolve" "test"`
promise对象的方法:then、catch、all、race
then
then方法接受两个参数,onFulfilled(状态变为fullfilled的回调)和onRejected(状态变为rejected的回调)。返回一个新的promise对象,返回的promise对象的状态与then的参数(onFulfilled、onRejected)和onFulfilled、onRejected方法中返回的值有关。
  1. then方法不传参数
    如果不传参数,则then方法返回的promise和调用then的promise的状态一致。
    更具体地,如果没有onFullfilled参数并且promise的状态为fullfilled,那么then方法返回的promise和调用then方法的promise状态一致;如果没有onRejected参数并且promise状态为rejected,那么then方法返回的promise和调用then方法的promise状态一致。
    可以简单地理解:如果上一个promise不处理,那就下一个promise处理。
var p = new Promise(resolve => {
    throw new Error('test');
});

p
.then(

)
.then(
    data => console.log('resolve', data),
    err => console.log('reject', err)
    
);

// 执行结果
reject Error: test
var p = new Promise(resolve => {
    resolve('test');
});

p
.then(
    undefined, () => {}
)
.then(
    data => console.log('resolve', data),
    err => console.log('reject', err)
    
);

// 执行结果
resolve test
  1. 回调不返回值
    无论onFullfilled中还是onRejected中,不返回值(即默认返回undefined),则then返回的新promise的状态变为fullfilled,值为undefined。
var p = new Promise(resolve => {
    resolve('test');
});

p
.then(
    () => {}
)
.then(
    data => console.log('resolve', data),
    err => console.log('reject', err)
    
);

// 执行结果
resolve undefined
var p = new Promise(resolve => {
    throw new Error('test');
});

p
.then(
    () => {},
    () => {}
)
.then(
    data => console.log('resolve', data),
    err => console.log('reject', err)
    
);

// 执行结果
resolve undefined
  1. 返回普通值
    无论onFullfilled中还是onRejected中,返回普通值,则then返回的新promise的状态变为fullfilled,值为这个值。普通值指的是,非promise对象、非thenable对象(含有then方法的对象)。
var p = new Promise(resolve => {
    resolve('test');
});

p
.then(
    () => {return 'a'},
    () => {return {b: 1}}
)
.then(
    data => console.log('resolve', data),
    err => console.log('reject', err)
    
);

// 执行结果
resolve a
var p = new Promise(resolve => {
    throw new Error('test');
});

p
.then(
    () => {return 'a'},
    () => {return {b: 1}}
)
.then(
    data => console.log('resolve', data),
    err => console.log('reject', err)
    
)

// 执行结果
resolve {b: 1}
  1. 返回promise
    无论onFullfilled中还是onRejected中,返回一个promise对象,则以该promise的任务和状态返回新的promise。
var p = new Promise(resolve => {
    throw new Error('test');
});

p
.then(
    () => {},
    () => {return Promise.resolve('yes');}
)
.then(
    data => console.log('resolve', data),
    err => console.log('reject', err)
    
);

// 执行结果
resolve yes
var p = new Promise(resolve => {
    resolve('test');
});

p
.then(
    () => {return Promise.reject('error');},
    () => {return {a: 1}}
)
.then(
    data => console.log('resolve', data),
    err => console.log('reject', err)
    
);

// 执行结果
reject error
  1. 返回thenable
    无论onFullfilled中还是onRejected中,返回一个thenable对象,则调用该对象的then方法,该then方法接收两个参数resolvePromise和rejectPromise,如果then中调用了resolvePromise,则返回的promise状态置为fullfilled,如果then中调用了rejectPromise,或者then中抛出异常,则返回的Promise状态置为rejected,在调用resolvePromise或者rejectPromise之前,返回的promise处于pending状态。
var p = new Promise((r) => {throw new Error('test')});


p
.then(
    () => ({then: function(resolvePromise, rejectPromise) {resolvePromise('resolvePromise')}}),
    e => ({then: function(resolvePromise, rejectPromise) {rejectPromise('rejectPromise')}})
)
.then(
    data => console.log('resolve', data),
    e => console.log('reject', e)
);

// 执行结果
reject rejectPromise
var p = new Promise((r) => {throw new Error('test')});


p
.then(
    () => ({then: function(resolvePromise, rejectPromise) {}}),
    e => ({then: function(resolvePromise, rejectPromise) {}})
)
.then(
    data => console.log('resolve', data),
    e => console.log('reject', e)
);

// 执行结果
promise 处于pending状态
var p = new Promise((r) => {throw new Error('test')});

p
.then(
    () => {return {then: function(resolvePromise, rejectPromise) {resolve('resolvePromise')}}},
    e => {return {then: function(resolvePromise, rejectPromise) {throw new Error('surprise')}}}
)
.then(
    data => console.log('resolve', data),
    e => {console.error('reject', e)}
);

// 执行结果
reject Error: surprise
  1. 抛出错误
    无论onFullfilled中还是onRejected中,抛出错误,则以rejected为状态返回新promise。
var p = new Promise(resolve => {resolve('test')});


p
.then(
    () => {throw new Error('1')},
    e => {return true}
)
.then(
    data => console.log('resolve', data),
    e => {console.error('reject', e)}
);

// 执行结果
reject Error: 1
var p = new Promise((r) => {throw new Error('test')});


p
.then(
    () => {return true},
    e => {throw new Error('2')}
)
.then(
    data => console.log('resolve', data),
    e => {console.error('reject', e)}
);

// 执行结果
reject Error: 2
catch

catch方法和then方法的reject回调用法相同,如果这时候任务处于rejected状态,则直接执行catch,catch的参数就是reject的reason;如果任务处于pending状态,则注册catch回调,等到状态变成rejected时候再执行。【阮一峰教程示例】

Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数

——《ES6入门教程》 阮一峰

p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err));

// 等同于
p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("rejected:", err));

all
Promise.all方法用于多个异步任务执行,当所有任务都正常完成时候,再做后面处理的场景。
Promise.all方法接收一个promise数组作为参数,返回一个promise,当参数的数组中的所有promise都resolve时候,返回的promise才会resolve;而若有一个参数的数组中的promise reject,返回的promise就会reject。
Promise.all方法返回的promise的then的第一个参数onFullfilled回调的参数也是一个数组,对应参数中的数组promise resolve的结果。

const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => {
	setTimeout(() => {
  	resolve(2);
  }, 1000);
});

Promise.all([p1, p2])
.then(
	([result1, result2]) => {console.log('resolve', result1, result2);}
);

// 执行结果
resolve 1 2
const p1 = Promise.reject(1);
const p2 = new Promise(resolve => {
	setTimeout(() => {
  	resolve(2);
  }, 1000);
});

Promise.all([p1, p2])
.then(
	([result1, result2]) => {console.log('resolve', result1, result2);},
    e => console.log('reject', e)
);

// 执行结果
reject 1
race

Promise.race方法用于多个异步任务执行,当有其中一个任务完成或失败时候,就执行后续处理的场景。
Promise.race接收一个promise数组作为参数,返回一个新的promise。当参数数组中其中一个promise resolve或者reject,返回的promise就相应地改变状态。

var p1 = Promise.reject(1);
var p2 = new Promise(resolve => {
	setTimeout(() => {
  	resolve(2);
  }, 1000);
});

Promise.race([p1, p2])
.then(
		data => {console.log('resolve', data);},
    e => {console.log('reject', e);}
);

// 执行结果
reject 1
allSettled

Promise.allSettled用于多个异步任务都结束(完成或者失败)时候,再执行后续任务的场景。
Promise.allSettled接收一个promise数组作为参数,返回一个promise。当参数数组中所有promise状态改变后,返回的promise变为fullfilled状态。
返回的promise的onFullfilled参数接收一个结果数组作为参数,数组对应Promise.allSettled传入的promise数组。结果数组每个元素是一个对象,格式固定:{status, value, reason},标识状态、resolve返回值、reject原因。

var p1 = Promise.reject(1);
var p2 = new Promise(resolve => {
	setTimeout(() => {
  	resolve(2);
  }, 1000);
});

Promise.allSettled([p1, p2])
.then(
	data => {console.log('resolve', data);},
);

// 执行结果
resolve [{status: "rejected", reason: 1}, {status: "fulfilled", value: 2}]
常见面试题

有3个异步任务,如何让他们顺序执行?

const task1 = () => {
	return new Promise((resolve, reject) => {
  	resolve(1);
  });
};

const task2 = () => {
	return new Promise((resolve, reject) => {
  	resolve(2);
  });
};

const task3 = () => {
	return new Promise((resolve, reject) => {
  	resolve(3);
  });
};

// 顺序执行3个任务
task1()
.then(task2)
.then(task3);

async/await

Promise虽然解决了回调地狱问题,但是缺点是有不少的样板代码,并且写代码时候还是通过then注册回调方式
async、await是语法糖,可以让开发者以写同步代码的形式写异步逻辑。
语法
如果方法中有await,方法需要加async修饰符。await后面跟一个promise。await表达式结果是promise resolve的值。

const task = () => {
	return new Promise(resolve => {
  	setTimeout(() => {
    	console.log('1');
      resolve('2');
    }, 1000);
  });
};

async function test() {
  console.log(0);
	const res = await task();
  console.log(res);
}

test();

// 执行结果
0
1
2

async方法返回一个promise。其resolve的值就是async方法中return的值。

async function task1() {
	return 'test';
}
task1()
.then(console.log);

// 执行结果
test

如果await后面返回的promise reject掉,需要用try catch语句捕获这个reject

const task = () => {
	return new Promise((resolve, reject) => {
  	setTimeout(() => {
      reject('test-reject');
    }, 1000);
  });
};

async function test() {
  try {
		const res = await task();
  }
  catch (e) {
  	console.log('error', e);
  }
}

test();

// 执行结果
error test-reject

手写Promise

实现promise

实现的思路可以参考从零开始手写Promise
完整代码如下

// https://zhuanlan.zhihu.com/p/144058361
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
    var _this = this
    this.state = PENDING; //状态
    this.value = undefined; //成功结果
    this.reason = undefined; //失败原因

    this.onFulfilled = [];//成功的回调
    this.onRejected = []; //失败的回调
    function resolve(value) {
        if(_this.state === PENDING){
            _this.state = FULFILLED
            _this.value = value
            _this.onFulfilled.forEach(fn => fn(value))
        }
    }
    function reject(reason) {
        if(_this.state === PENDING){
            _this.state = REJECTED
            _this.reason = reason
            _this.onRejected.forEach(fn => fn(reason))
        }
    }
    try {
        executor(resolve, reject);
    }
    catch (e) {
        reject(e);
    }
}

Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}
Promise.prototype.then = function (onFulfilled, onRejected) {
    //_this是promise1的实例对象
    var _this = this;
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

    var promise2 = new Promise((resolve, reject) => {
        if (_this.state === FULFILLED) {
            setTimeout(() => {
                try {
                    let x = onFulfilled(_this.value);
                    resolvePromise(promise2, x, resolve, reject);
                }
                catch (error) {
                    reject(error)
                }
            });
        }
        else if (_this.state === REJECTED) {
            setTimeout(()=>{
                try {                    
                    let x = onRejected(_this.reason);
                    resolvePromise(promise2, x ,resolve, reject);
                }
                catch (error) {
                    reject(error);
                }
            });
        }
        else if(_this.state === PENDING) {
            _this.onFulfilled.push(() => {
                setTimeout(() => {
                    try {                        
                        let x = onFulfilled(_this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    }
                    catch (error) {
                        reject(error);
                    }
                });
            });
            _this.onRejected.push(() => {
                setTimeout(() => {
                    try {                        
                        let x = onRejected(_this.reason);
                        resolvePromise(promise2, x ,resolve, reject);
                    }
                    catch (error) {
                        reject(error);
                    }
                })
            });
        }
    });

    return promise2;
};

function resolvePromise(promise2, x, resolve, reject){
    if (promise2 === x) {
        reject(new TypeError('Chaining cycle'))
    }
    if (x && typeof x === 'object' || typeof x === 'function') {
        let used;
        try {
            let then = x.then
            if (typeof then === 'function') {
                then.call(
                    x,
                    y => {
                        if (used) return;
                        used = true
                        resolvePromise(promise2, y, resolve, reject)
                    },
                    r =>{
                        if (used) return;
                        used = true;
                        reject(r);
                    }
                );
            }
            else {
                if (used) return;
                used = true;
                resolve(x);
            }
        }
        catch(e) {
            if (used) return;
            used = true;
            reject(e);
        }
    }
    else {
        resolve(x);
    }
}

module.exports = Promise;

可以通过测试工具验证自己实现Promise的完整性:Promise测试工具 promises-aplus-tests,这个工具提供872个测试用例,完全通过后就能够说明实现的Promise符合规范。
promises-aplus-tests的使用方法:

  1. 安装promises-aplus-tests
  2. 导出自己实现的Promise
  3. 编写test脚本,运行脚本即可测试Promise
var promisesAplusTests = require("promises-aplus-tests");
var adapter = require('./promise');

promisesAplusTests(adapter, function (err) {
    // All done; output is in the console. Or check `err` for number of failures.
});

实现Promise.all、Promise.race、Promise.any、Promise.allSettled

这3个方法都接受一个promise数组作为参数,返回一个promise
它们区别是:
● Promise.any() 有一个promise成功就算成功,全部promise失败才算失败,成功的话返回成功的结果,全部失败的话,抛出’AggregateError: All promises were rejected’。
● Promise.all() 全部promise成功才算成功,一个promise就算失败,成功的话,返回成功的数据数组,失败的话抛出最先失败的promise的reason。
● Promise.race() 最先的promise完成则返回,promise结果和最先完成的promise一致。
● Promise.allSettled()等所有promise都完成返回,返回的是一个结果数组,结果反映了状态和成功信息/错误原因。

function all(arr) {
    return new Promise((resolve, reject) => {
        let isComplete = false;
        const resolveDataList = new Array(arr.length).fill(undefined);
        const onFullfilled = (data, i) => {
            if (isComplete) {
                return;
            }
            resolveDataList[i] = data;
            if (resolveDataList.every(item => item !== undefined)) {
                resolve(resolveDataList);
            }
        };
        const onRejected = reason => {
            if (isComplete) {
                return;
            }
            isComplete = true;
            reject(reason);
        }
        arr.forEach((promise, index) => {
            promise.then(
                data => {onFullfilled(data, index)},
                onRejected
            );
        });
    });
}


function race(arr) {
    return new Promise((resolve, reject) => {
        let isComplete = false;
        const onFullfilled = data => {
            if (isComplete) {
                return;
            }
            isComplete = true;
            resolve(data);
        };
        const onRejected = reason => {
            if (isComplete) {
                return;
            }
            isComplete = true;
            reject(reason);
        };
        arr.forEach(promise => {
            promise.then(
                onFullfilled, onRejected
            );
        });
    });
}

function any(arr) {
    return new Promise((resolve, reject) => {
        let isComplete = false;
        const rejectDataList = new Array(arr.length).fill(undefined);
        const onFullfilled = data => {
            if (isComplete) {
                return;
            }
            isComplete = true;
            resolve(data);
        };
        const onRejected = (reason, i) => {
            if (isComplete) {
                return;
            }
            rejectDataList[i] = reason;
            if (rejectDataList.every(item => item !== undefined)) {
                reject('AggregateError: All promises were rejected');
            }
        }
        arr.forEach((promise, index) => {
            promise.then(
                onFullfilled,
                reason => {onRejected(reason, index);}
            );
        });
    });
}

function allSettled(list) {
    return new Promise((resolve, reject) => {
        const result = new Array(list.length).fill(undefined);
        const onStatusChange = () => {
            if (result.filter(Boolean).length === list.length) {
                resolve(result);
            }
        }
        list.forEach((promise, index) => {
            promise.then(
                data => {
                    result[index] = {status: 'fullfilled', data};
                    onStatusChange();
                },
                reason => {
                    result[index] = {status: 'rejected', reason};
                    onStatusChange();
                }
            );
        });        
    });
}

实现并发请求控制

import axios from 'axios';

function multiRequest(urls, maxNum) {
    return new Promise((resolve, reject) => {
        const requestURLS = [...urls];
        const result = {};
        let urlPool = [];
    
        const createTask = (url) => {
            urlPool.push(url);
            const onComplete = (error, res) => {
                if (urlPool.length === 0) {
                    resolve(urls.map(url => result[url]));
                }
                result[url] = error || res;
                urlPool = urlPool.filter(value => value !== url);
                if (urlPool.length < maxNum && requestURLS.length) {
                    createTask(requestURLS.pop());
                }
            };
            axios.get(url)
                .then(
                    res => {
                        onComplete(null, res);
                    },
                    error => {
                        onComplete(error);
                    }
                );
        };
        while (requestURLS.length && urlPool.length < maxNum) {
            createTask(requestURLS.pop());
        }
    });
}

promise面试题:说出代码执行结果

说明

先来看一个字节面试的真题

console.log(1);
new Promise(resolve => {
    resolve();
    console.log(2);
}).then(() => {
    console.log(3);
})
setTimeout(() => {
    console.log(4);
}, 0);
console.log(5);

这是一个经典的promise的代码执行结果的面试题,下面我们说明一下这类题目应该如何解答,
在前端面试中,promise的代码执行结果是常出现的一个题目。这个题目主要考察应聘者对promise的理解和对事件循环的理解。
实际上只要掌握几个简单的规则,这种题目就可以轻而易举地解答。
通常这类题目的代码中,主要会出现promise/async-await、setTimeout/setInterval。其中async-await是promise语法糖,所以逻辑是一样的,setInterval和setTimeout的回调都是放在宏任务队列中,规则相同。因此只要掌握了promise和setTimeout的执行规则,就能够解答这类题目。

规则

我们需要掌握5条规则就可以解答这类题目。这5条规则涉及两个知识点:promise和事件循环。

promise
1. new Promise时候马上执行
new Promise(resolve => {
    console.log('test');
    resolve();
});

上面的代码执行时候,会立刻打印"test",所以Promise实例化时候传入的回调是同步执行的

2. resolve或者reject之后状态不再改变,但后面代码会执行
new Promise(resolve => {
    resolve();
    console.log('test');
    reject();
});

上面的Promise在回调中执行了resolve、打印"test"、reject,这3个方法都会执行,但是reject不会生效,因为执行resolve后promise状态已经变为fullfilled。

事件循环
  1. promise的then中的回调放入微任务队列,setTimeout放入宏任务队列
    promise.catch的回调一样,也会放入微任务队列。

  2. 调用栈中代码执行完之后,先取微任务队列中的任务执行,直到微任务队列为空

  3. 微任务队列为空,取宏任务队列中的一个任务开始执行,然后重复上一步,直到宏任务队列为空
    可以简单描述为:一个宏任务 + 所有微任务 ->一个宏任务 + 所有微任务……,循环往复。

解题思路

根据上面的规则,我们可以总结解题的思路是

  1. 先列出调用栈、宏任务队列、微任务队列及初始状态:调用栈是当前的代码,最开始宏任务队列和微任务队列为空。
  2. 遇到new Promise,直接执行Promise的函数参数
  3. 遇到resolve/reject,改变状态
  4. 遇到promise.then/promise.catch,放入微任务队列、遇到setTimeout放入宏任务队列
  5. 调用栈执行完后,从微任务队列取任务放到调用栈中执行
  6. 微任务队列执行完后,从宏任务队列取一个放到调用栈执行,然后执行上一步,直到宏任务队列为空

下面我们通过最开始提到的题目来演示一下如何操作

范例

题目:说出下面代码执行结果:

console.log(1);
new Promise(resolve => {
    resolve();
    console.log(2);
}).then(() => {
    console.log(3);
})
setTimeout(() => {
    console.log(4);
}, 0);
console.log(5);

解题过程

  1. 初始状态
    代码都在调用栈中

  2. 第一步
    执行当前调用栈,先打印1,然后执行new Promise,打印2,然后将.then回调放到微任务队列,将setTimeout回调放到宏任务队列,然后打印5,调用栈为空
    打印1,2,5

  3. 第二步
    查看微任务队列,取出promise.then的回调放入调用栈中执行,执行完后调用栈为空。
    打印3

  4. 第三步
    微任务队列为空,所以查找宏任务队列中的setTimeout回调,放入调用栈中,执行完后为空。
    打印4

  5. 结束
    调用栈为空,执行结束

下面通过几个面试真题加深理解吧。

真题

1、说出代码执行结果

const promise = new Promise((resolve,reject)=>{
    console.log(1);
    resolve();
    console.log(2);
    reject()
})
setTimeout(()=>{console.log(5)},0)
promise.then(()=>{console.log(3)})
.then(()=>{console.log(6)})
.catch(()=>{console.log(7)})
console.log(4)
解析
答案是1,2,4,3,6,5

解析
首先new Promise时候打印1和2,因为new Promise时候会立即执行传入的方法
然后后面代码都是异步代码,先将setTimeout的回调加入宏任务队列,再把promise.then放入到微任务队列,然后直接执行最后一句,打印4
这样宏任务代码执行完了,接下来开始执行微任务队列中的任务,由于promise resolve,因为promise resolve之后状态不会再改变,因此不会执行到reject的对调,所以打印3和6
微任务队列为空,再到宏任务队列中查找任务,找到setTimeout回调执行,打印5
调用栈、宏任务队列、微任务队列都为空,代码执行结束。

2、说出代码执行结果

const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve();
        }, 0);
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));
first().then((arg) => {
    console.log(arg);
});
console.log(4);
解析
3, 7, 4, 1, 2, 5

解析
首先定义first
然后执行first,然后执行new Promise传入的方法,先打印3
又new Promise,执行其中传入的方法,打印7
执行setTimeout,将回调放入宏任务队列
执行resolve(1),将内部promise状态置为fullfilled,值为1
执行resolve(2),将外部promise状态置为fullfilled,值为2
执行内部promise.then方法,将回调加入微任务队列
执行first().then,即外部的promise,将回调加入到微任务队列
调用栈为空,开始从微任务队列拿取任务,首先拿到内部promise的回调,打印其值1
然后从微任务队列中拿取外部的promise的回调,打印其值2
此时微任务队列为空,开始从宏任务队列中拿取任务,即setTimeout回调,打印5。
调用栈,宏任务队列和微任务队列都为空,执行结束。