咱们先看看这几个来自大厂的面试题javascript
面试题1:java
const promise = new Promise(function(resolve,reject){
console.log(1)
resolve()
console.log(2)
})
console.log(3)
复制代码
面试题2:git
setTimeout(function () {
console.log(1);
}, 0)
new Promise(function (resolve) {
console.log(2);
for (var i = 0; i < 100; i++) {
i == 99 && resolve();
}
console.log(3);
}).then(function () {
console.log(4);
})
console.log(5);
复制代码
面试题3:github
Promise.resolve(1)
.then((res)=>{
console.log(res)
return 2
})
.catch( (err) => 3)
.then(res=>console.log(res))。
复制代码
面试题4:面试
Promise.resolve(1)
.then( (x) => x + 1 )
.then( (x) => {throw new Error('My Error')})
.catch( () => 1)
.then( (x) => x + 1)
.then((x) => console.log(x))
.catch( (x) => console.log(error))
复制代码
若是你看完这些题一脸懵逼,恭喜你,你能够继续往下看了,else
,出门左拐大佬。编程
首先简单介绍一些Promise
。promise
Promises
对象是CommonJS
工做组提出的一种规范,目的是为异步操做提供统一接口。bash
那么,什么是Promises
?首先,它是一个对象,也就是说与其余JavaScript
对象的用法,没有什么两样;其次,它起到代理做用(proxy)
,使得异步操做具有同步操做(synchronous code)
的接口,即充当异步操做与回调函数之间的中介,使得程序具有正常的同步运行的流程,回调函数没必要再一层层包裹起来。app
简单说,它的思想是,每个异步任务马上返回一个Promise
对象,因为是马上返回,因此能够采用同步操做的流程。异步
Promise
表示一个异步操做的最终结果,与之进行交互的方式主要是then
方法,该方法注册了两个回调函数,用于接收promise
的终值或本promise
不能执行的缘由。
下图是一张Promise
的API结构图。(引自https://github.com/leer0911/myPromise)
const promise = new Promise(function(resolve,reject){
console.log(1)
resolve()
console.log(2)
})
console.log(3)
// 1
// 2
// 3
复制代码
解析:
Promise
相似于XMLHttpRequest
,从构造函数Promise
来建立一个新Promise
对象做为接口。
要想建立一个Promise
对象吗可使用new
来调用Promise
的构造器来进行实例化。
var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});
复制代码
new Promise
的时候, 须要传递一个executor
执行器 ,执行器函数会默认被内部所执行。借用VSCode
编辑器咱们能够看到一些内部的参数和返回值。
new Promise
内部的执行器会当即执行它里面的代码,这里的resolve()
并不会阻塞下面的代码执行,咱们能够理解new Promise(function(){...})
这个就是一段同步代码而已。因此第一道题的答案显而易见。
巩固一下,下面的代码你必定能够准确的得出结果了。
setTimeout(function () {
console.log('setTimeout')
}, 0);
let p = new Promise(function (resolve,reject) {
resolve();
console.log('a')
});
复制代码
结果是:
// a
// setTimeout
复制代码
这里会有EventLoop
的一些知识,补充知识连接:说一说javascript的异步编程,到目前来讲能够获得两点:
1. 咱们彻底能够把`new Promise(function(){...})`当作是同步代码。
2. `Promise`会优先于`setTimeout`执行。
复制代码
不一样于老式的传入回调,在应用 Promise
时,咱们将会有如下约定:
Promise
最直接的好处就是链式调用setTimeout(function () {
console.log(1);
}, 0)
new Promise(function (resolve) {
console.log(2);
for (var i = 0; i < 100; i++) {
i == 99 && resolve();
}
console.log(3);
}).then(function () {
console.log(4);
})
console.log(5);
// 2
// 3
// 5
// 4
// 1
复制代码
解析:
能够把 Promise
当作一个状态机。初始是 pending
状态,能够经过函数 resolve
和 reject
,将状态转变为 resolved
或者 rejected
状态,状态一旦改变就不能再次变化。
then
函数会返回一个 Promise
实例,而且该返回值是一个新的实例而不是以前的实例。由于 Promise 规范规定除了 pending
状态,其余状态是不能够改变的,若是返回的是一个相同实例的话,多个 then
调用就失去意义了。
MDN
中对于promise
的介绍一个小案例很是的简单易懂,以下:
一个常见的需求就是连续执行两个或者多个异步操做,这种状况下,每个后来的操做都在前面的操做执行成功以后,带着上一步操做所返回的结果开始执行。咱们能够经过创造一个 Promise chain
来完成这种需求。
then
函数会返回一个新的 Promise
,跟原来的不一样:
const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);
复制代码
或者
const promise2 = doSomething().then(successCallback, failureCallback);
复制代码
第二个对象(promise2)
不只表明doSomething()
函数的完成,也表明了你传入的 successCallback
或者failureCallback
的完成,这也多是其余异步函数返回的 Promise
。这样的话,任何被添加给 promise2
的回调函数都会被排在 successCallback
或 failureCallback
返回的 Promise
后面。
特别重申一下,then
函数必定返回一个新的 Promise
,跟原来的不一样,下面的是错误的应用:
Promise
对象的运行结果,最终只有两种。
fulfilled
rejected
promise.then(onFulfilled, onRejected);
复制代码
promise
对象的then
方法用来添加回调函数。它能够接受两个回调函数,第一个是操做成功(fulfilled
)时的回调函数,第二个是操做失败(rejected
)时的回调函数(能够不提供)。一旦状态改变,就调用相应的回调函数。
onFulfilled
和 onRejected
都是可选参数。
若是 onFulfilled
是函数,当 promise
执行结束后其必须被调用,其第一个参数为 promise
的终值,在 promise
执行结束前其不可被调用,其调用次数不可超过一次
若是 onRejected
是函数,当 promise
被拒绝执行后其必须被调用,其第一个参数为 promise
的据因,在 promise
被拒绝执行前其不可被调用,其调用次数不可超过一次
onFulfilled
和 onRejected
只有在执行环境堆栈仅包含平台代码 ( 指的是引擎、环境以及 promise
的实施代码 )时才可被调用
实践中要确保 onFulfilled
和 onRejected
方法异步执行,且应该在 then
方法被调用的那一轮事件循环以后的新执行栈中执行。
onFulfilled
和 onRejected
必须被做为函数调用即没有 this
值 ( 也就是说在 严格模式(strict
) 中,函数 this
的值为 undefined
;在非严格模式中其为全局对象。)
then
方法能够被同一个 promise
调用屡次
then
方法必须返回一个 promise
对象
为了不意外,即便是一个已经变成 resolve
状态的 Promise
,传递给 then
的函数也老是会被异步调用:
Promise.resolve().then(() => console.log(2));
console.log(1); // 1, 2
复制代码
至此,咱们再回过头来看面试题:
setTimeout(function () {
console.log(1);
}, 0)
new Promise(function (resolve) {
console.log(2);
for (var i = 0; i < 100; i++) {
i == 99 && resolve();
}
console.log(3);
}).then(function () {
console.log(4);
})
console.log(5);
// 2
// 3
// 5
// 4
// 1
复制代码
new Promise
构造器以后,会返回一个promise
对象,对于这个promise
对象,咱们调用他的then
方法来设置resolve
后的回调函数。
该promise
对象会在for
循环知足i==99
时被resolve()
,这时then
的回调函数会被调用。
基于第一道题的基础,咱们知道最后被打印的必定是1
,而后是2
,接着是3
,因为.then
是一个异步函数,用来接受promise
的返回结果,很显然应该是5
,setTimeout
因为EventLoop
的缘由是最后执行,因此后面是4
,最后是1
.
Promise.resolve(1)
.then((res)=>{
console.log(res)
return 2
})
.catch( (err) => 3)
.then(res=>console.log(res))
// 1
// 2
复制代码
解析:
静态方法Promise.resolve(value)
能够认为是new Promise()
方法的快捷方式。 好比Promise.resolve(1);
,能够认为是一下代码的语法糖:
new Promise(function(resolve){
resolve(1);
});
复制代码
在这段代码中的 resolve(1);
会让这个 promise
对象当即进入肯定(即resolved
)状态, 并将 1
传递给后面then
里所指定的 onFulfilled
函数。
方法Promise.resolve(value)
的返回值也是一个promise
对象,因此咱们能够像下面那样 接着对其返回值进行.then
调用。
Promise.resolve(42).then(function(value){
console.log(value)
})
复制代码
简单总结一下 Promise.resolve
方法的话,能够认为它的做用就是将传递给它的参数填 充(Fulfilled
)到promise
对象后并返回这个promise
对象。
Promise.resolve(value)
方法返回一个以给定值解析后的Promise
对象。但若是这个值是个thenable
(即带有then
方法),返回的promise
会“跟随”这个thenable
的对象,采用它的最终状态(指resolved/rejected/pending/settled
);若是传入的value
自己就是promise
对象,则该对象做为Promise.resolve
方法的返回值返回;不然以该值为成功状态返回promise
对象。
语法为:
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);
复制代码
咱们知道.then()
一样是返回一个promise
对象才能实现链式调用,因此连续的.then()
是一样的道理,多个 then
方法调用串连在了一块儿,各函数也会严 格按照 resolve → then → then → then
的顺序执行,而且传给每一个 then
方法的 value
的值都是前一个promise
对象经过 return
返回的值。而且上面已经提到then
方法每次都会建立并返回一个新的promise
对象。
then
方法执行完会判断返回的结果,若是是promise
会把这个promise
执行,会取到它的结果。成功态(onFulfilled
)和失败态(onRejected
)。若是成功了就会把成功的结果传递给后面then
里所指定的 onFulfilled
函数,失败了传递给onRejected
函数。
Promise.resolve(1)
.then((value)=>{
console.log('value',value)
},
(reason)=>{
console.log('reason',reason)
})
// value 1
复制代码
Promise.reject(2)
.then((value)=>{
console.log('value',value)
},
(reason)=>{
console.log('reason',reason)
})
// reason 2
复制代码
上面的两个案例能够很清楚的看到,promise
分别在成功态和失败态的时候传递给后面的then
函数的调用输出结果。下一层的then
是调成功仍是失败是根据上面的promise
返回的是成功仍是失败决定的。
说到这里第三题的答案已经不用说了。
Promise.resolve(1)
.then( (x) => x + 1 )
.then( (x) => {throw new Error('My Error')})
.catch( () => 1)
.then( (x) => x + 1)
.then((x) => console.log(x))
.catch( (x) => console.log(error))
// 2
复制代码
在一个失败操做(即一个 catch
)以后能够继续使用链式操做,即便链式中的一个动做失败以后还能有助于新的动做继续完成。请阅读下面的例子:
new Promise((resolve, reject) => {
console.log('Initial');
resolve();
})
.then(() => {
throw new Error('Something failed');
console.log('Do this');
})
.catch(() => {
console.log('Do that');
})
.then(() => {
console.log('Do this whatever happened before');
});
复制代码
输出结果以下:
Initial
Do that
Do this whatever happened before
复制代码
注意,因为“Something failed”
错误致使了拒绝操做,因此“Do this”
文本没有被输出。
解析:
这道题惟一的疑惑多是在第二个then
这里,这个错误状态为何没有被打印出来,咱们知道catch
是用来捕获错误的,可是这里catch
是能够捕获到错误的,可是这段代码没有对捕获的错误进行处理而是继续返回了1
做为下一个Promise
的参数,因此在第三个then
中咱们获取到了一个1
做为成功态,而后又对其进行+1
处理返回给了下一个then
的promise
的成功态,这时候最后一个then
的第一个函数onFulfilled
就能获取到一个value
打印出来就是2
,没有错误信息返回因此最后的catch
没有输出。
咱们也能够对上面的题改一种写法,就是另外一种答案了:
Promise.resolve(1)
.then( (x) => x + 1 )
.then( (x) => {throw new Error('My Error')})
.catch( (err) => console.log(err))
.then( (x) => {
console.log(x)
return x + 1
})
.then((x) => console.log(x))
.catch( (x) => console.log(error))
// Error: My Error
at Promise.resolve.then.then
// undefined
// NaN
复制代码
咱们在第一个catch
中对错误信息进行了处理,可是咱们没有给下面的then
返回一个成功态的结果,因此默认是undefined
,这样就会致使后面的结果彻底不同。
你们能够把这些面试题进行屡次变形,改写来去理解promise
的执行顺序,以及参数传递,这样就能绕过更多的坑。
以上内容若是错误,欢迎指正,共同进步~