1. 介绍 Promise函数,它的内部实现是什么?
Promise 是异步编程的一种解决方案,比传统的解决方案回调函数CallBack更合理,。Promise
对象有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。一旦状态确定就无法改变,Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。
Promise
也有一些缺点。首先,无法取消Promise
,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise
内部抛出的错误,不会反应到外部。第三,当处于pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise的内部实现代码:
const promise = new Promise(function(resolve, reject) {
// 注意,Promise 新建后就会立即执行
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});
resolve表示异步操作成功,reject表示异步操作失败,通过then来监听结果,then中的第二个监听失败的参数时可选的,可以使用catch来代替,如:
promise.then(function(value) {
// success
}).catch(function(error){
// failure
});
关于catch的机制,还有一点值得注意,它可以捕获多层then抛出的错误,如:
promise.then(function(value) {
// return Promise(...)
}).then(function(){
// success
}).catch(function(error){
// failure
});
无论是在第一个then执行过程中发生错误,还是第二个then发生错误,都会在最后的catch中统一捕获。
关于Promise嵌套,如:
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
p1
和p2
都是 Promise 的实例,但是p2
的resolve
方法将p1
作为参数,即一个异步操作的结果是返回另一个异步操作。这时p1
的状态就会传递给p2
,也就是说,p1
的状态决定了p2
的状态。即由于p2
返回的是另一个 Promise,导致p2
自己的状态无效了。所以看p2的then或catch函数响应的是哪个异步状态,那就是p1。
另外还有一点,resolve和reject不等同于return,不会终止代码继续往下执行,但一般来说,调用resolve
或reject
以后,Promise 的使命就完成了,后继操作应该放到then
方法里面,而不应该直接写在resolve
或reject
的后面。所以,最好在它们前面加上return
语句,这样就不会有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
关于Promise.prototype.finally(),不管promise
最后的状态,在执行完then
或catch
指定的回调函数以后,都会执行finally
方法指定的回调函数。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
关于Promise.all(),用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
Promise.all
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise 实例,如果不是,就会先调用Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。p
的状态由p1
、p2
、p3
决定,分成两种情况:
- 只有
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的resolve返回值组成一个数组,传递给p
的回调函数,作为p.then(resolver, reject)中resolver的回调参数。 - 只要
p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数,并被catch捕获或作为p.then(resolver, reject)中reject的回调参数。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了
其中p2报错,错误被Promise.all().catch捕获。但有一种情况,就是p1或p2也加了catch,则错误会在p1或p2的catch中捕获,不会冒泡到Promise.all().catch,此时Promise.all()认为虽然你发生错误,但实际上你自己catch之后返回的是一个新的 Promise 实例,最终执行resolve。如:
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
关于Promise.race(),同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
关于Promise.resolve(),实际上是返回一个新的Promise对象
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
Promise.reject() 同理。
2. 介绍 Generator 函数及其异步应用?
Generator 函数是 ES6 提供的一种异步编程解决方案,语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
Generator 函数有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态。
关于Generator基本用法:
function* generatorFun() {
yield 'hello';
yield 'world';
return 'ending';
yield 'after return';
}
var fun = generatorFun();
console.log(fun); // Generator对象
console.log(fun.next()); // {value: "hello", done: false} value表示表达式的计算结果,done表示遍历是否已经结束
console.log(fun.next()); // {value: "world", done: false}
console.log(fun.next()); // {value: "ending", done: true}
console.log(fun.next()); // {value: undefined, done: true}
即该函数有三个状态:hello,world 和 return 语句,由于after return已经在return后面,所以代码不会被执行。调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的Generator指针对象。要执行函数内的代码,必须使用next方法,next方法的返回值是一个{value: xxx, done:xxx}对象,同时使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。换言之,Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法可以恢复执行。
关于next 方法的参数,yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
a的执行过程:
- 第一个next,value值等于执行yield (x + 1) ,为6,由于
yield
表达式本身没有返回值,所以y等于undefined。 - 第二个next,value值等于执行yield (y / 3),由于y等于undefined,所以value为NaN,由于
yield
表达式本身没有返回值,z等于undefined。 - 第三个next,value值等于执行(x + y + z),实际上是 (5 + undefined + undefined),所以value为NaN。
b的执行过程:
- 第一个next,value值等于执行yield (x + 1) ,为6,由于
yield
表达式本身没有返回值,所以y等于undefined。 - 第二个next(12),入参12等于上一次next表达式的赋值,即y = 2 * 12即24,value值等于执行yield (y / 3),所以value为8
- 第二个next(13),入参13等于上一次next表达式的赋值,即z = 13,value值等于执行(x + y + z) = 5 + 24 + 13即42。
关于for...of 循环,不需要next方法就可以执行yield表达式,只要遇到done为true则马上停止执行。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
关于异常捕获,是如果内部有catch,则内部优先捕获,且只捕获一次,其余情况均由外部捕获。另外,一旦发生异常,则Generator函数停止继续执行。
var errorHandle = (msg) => {
throw new Error();
}
function* generatorFun() {
try {
yield 1;
yield errorHandle('Error Msg A');
yield 2; // 没机会继续执行
yield errorHandle('Error Msg B'); // 没机会继续执行
} catch(e) {
console.log("内部捕获:", e);
}
}
var hw = generatorFun();
try {
console.log(hw.next());
console.log(hw.next());
console.log(hw.next());
} catch(e) {
console.log("外部捕获:", e);
}
// {value: 1, done: false}
// 内部捕获: Error
有些情况需要从外部抛出异常,可以使用Generator.prototype.throw(),规则:内部优先捕获,且只捕获一次
var errorHandle = (msg) => {
throw new Error();
}
function* generatorFun() {
try {
yield 1;
yield errorHandle('Error Msg A');
yield 2;
yield errorHandle('Error Msg B');
} catch(e) {
console.log("内部捕获:", e);
}
}
var hw = generatorFun();
try {
console.log(hw.next());
console.log(hw.throw('Error throw A'));
console.log(hw.next());
console.log(hw.throw('Error throw B'));
} catch(e) {
console.log("外部捕获:", e);
}
// {value: 1, done: false}
// 内部捕获: Error throw A
// 外部捕获: Error throw B
关于终结状态遍历,可以使用Generator.prototype.return(),入参为最终输出的value值,且done属性总是返回true。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
关于yield* 表达式,是指Generator函数中嵌套Generator函数。如果不加*号将无效。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
for(let v of bar()) {
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
关于异步加载的应用,如实现顺序和并发请求加载多张图片:
function loadImg(name) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(name);
}, 2000)
});
}
function* funGenerator() {
yield loadImg('/img1.png');
yield loadImg('/img2.png');
yield loadImg('/img3.png');
}
// 异步顺序加载
function sequentialLoad(generator, callback) {
var n = generator.next();
if(!n.done) {
n.value.then((response) => {
console.log(response);
sequentialLoad(generator, callback);
}).catch(function(err) {
callback({
state: 'failed',
msg: '加载失败:' + err
});
})
} else {
callback({
state: 'success',
msg: '加载完成'
});
}
}
// 异步并发加载
function concurrentLoad(generator, callback) {
var count = 0;
for(var p of generator) {
count++;
p.then((response) => {
count--;
if(count == 0) {
callback({
state: 'success',
msg: '加载完成'
});
}
}).catch(function(err) {
callback({
state: 'failed',
msg: '加载失败:' + err
});
})
}
}
var generator = funGenerator();
/**顺序加载*/
sequentialLoad(generator, function(response) {
console.log(response);
});
/**并发加载*/
//concurrentLoad(generator, function(response) {
// console.log(response);
//});
3. 对async、await的理解,内部原理?
async、await其实是Generator函数的语法糖,把*号换成了async,把yield换成了await,另外,async、await极大的补足了Generator函数的缺点,包括:
- async、await定义的函数不需要next即就可以执行,即自带执行器。
- async、await定义的函数返回的是一个Promise对象,该对象通常使用then和catch获取结果或监听异常。
- async、await函数
return出来的
值,将会作为then
回调函数的第一个参数。
function loadImg(name) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(name);
}, 2000)
});
}
const asyncReadFile = async function() {
var promiseArray = [];
const f1 = await loadImg('/img1.png'); // 等待2秒
console.log(f1) // 打印'/img1.png'
const f2 = await loadImg('/img2.png'); // 等待2秒
console.log(f2) // 打印'/img1.png'
promiseArray.push(f1);
promiseArray.push(f2);
return promiseArray;
};
var asyncFun = asyncReadFile();
// return出来的返回值,将会作为then回调函数的第一个参数
asyncFun.then(function(response) {
console.log(response); // 4秒钟后,打印["/img1.png", "/img2.png"]
}).catch(function(err) {
console.log('error:',err);
});
在上面代码中,当async函数内有多个await,将会按顺序执行(也叫继发执行),一个await完成后才开始执行下一个await。但只要其中有一个await的报出Promise.reject或函数内抛出错误异常throw new Error(),则函数停止往下执行且会被catch捕获。
那么,有时我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await
放在try...catch
结构里面,这样不管这个异步操作是否成功,第二个await
都会执行。
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {}
return await Promise.resolve('hello world');
}
f().then(v => console.log(v))
async、await 异步并发执行算法:
function loadImg(name) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(name);
}, 2000)
});
}
async function logInOrder(urls) {
// 并发读取远程URL
const textPromises = urls.map(async url => {
const response = await loadImg(url);
return response;
});
// 按次序输出
for(const textPromise of textPromises) {
console.log(await textPromise);
}
}
var urls = ['/img1.png', '/img2.png'];
logInOrder(urls);
async、await 异步顺序执行算法:
function loadImg(name) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(name);
}, 2000)
});
}
async function logInOrder(urls) {
for (const url of urls) {
const response = await loadImg(url);
console.log(response);
}
}
var urls = ['/img1.png', '/img2.png'];
logInOrder(urls);
4. 通过什么可以做到并发请求?
- 通过Promise.all函数实现并发。
- 通过async、await 配合Array.map实现并发。
- 通过Generator配合for...of实现并发,实现过程请看文章对Generator介绍的最后一个例子。
Promise.all实现并发:
const p1 = new Promise((resolve, reject) => {
console.log('p1');
setTimeout(function() {
resolve('hello');
}, 2000)
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
console.log('p2');
setTimeout(function() {
reject('报错了');
}, 2000)
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
async、await 配合Array.map实现并发:
function loadImg(name) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(name);
}, 2000)
});
}
async function logInOrder(urls) {
// 并发读取远程URL
const textPromises = urls.map(async url => {
const response = await loadImg(url);
return response;
});
// 按次序输出
for(const textPromise of textPromises) {
console.log(await textPromise);
}
}
var urls = ['/img1.png', '/img2.png'];
logInOrder(urls);