时间: 2019-11-17阅读: 67标签: 面试前言
Promise
1.高级 WEB 面试会让你手写一个Promise,Generator 的 PolyFill(一段代码);2.在写之前我们简单回顾下他们的作用;3.手写模块见PolyFill.
是什么
Promise是异步编程的一种解决方案。Promise对象表示了异步操作的最终状态(完成或失败)和返回的结果。
其实我们在jQuery的ajax中已经见识了部分Promise的实现,通过Promise,我们能够将回调转换为链式调用,也起到解耦的作用。
源码
怎么用
Promise接口的基本思想是让异步操作返回一个Promise对象
源码地址请戳,原创码字不易,欢迎 star
三种状态和两种变化途径
Promise对象只有三种状态。
- 异步操作“未完成”(pending)
- 异步操作“已完成”(resolved,又称fulfilled)
- 异步操作“失败”(rejected)
这三种的状态的变化途径只有两种。
- 异步操作从“未完成”到“已完成”
- 10bet,异步操作从“未完成”到“失败”。
这种变化只能发生一次,一旦当前状态变为“已完成”或“失败”,就意味着不会再有新的状态变化了。因此,Promise对象的最终结果只有两种。
异步操作成功,Promise对象传回一个值,状态变为resolved。
异步操作失败,Promise对象抛出一个错误,状态变为rejected。
如果觉得看文章太啰嗦,可以直接 git clone ,直接看代码
生成Promise对象
通过new Promise来生成Promise对象:
var promise = new Promise(function(resolve, reject) {
// 异步操作的代码
if (/* 异步操作成功 */){
resolve(value)
} else {
reject(error)
}
})
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。
resolve会将Promise对象的状态从pending变为resolved,reject则是将Promise对象的状态从pending变为rejected。
Promise构造函数接受一个函数后会立即执行这个函数
var promise = new Promise(function () {
console.log('Hello World')
})
// Hello World
1.Promise1.1 作用
then和catch回调
Promise对象生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。第二个函数是可选的。分别称之为成功回调和失败回调。成功回调接收异步操作成功的结果为参数,失败回调接收异步操作失败报出的错误作为参数。
var promise = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('成功')
}, 3000)
})
promise.then(function (data){
console.log(data)
})
// 3s后打印'成功'
catch方法是then(null, rejection)的别名,用于指定发生错误时的回调函数。
var promise = new Promise(function (resolve, reject) {
setTimeout(function () {
reject('失败')
}, 3000)
})
promise.catch(function (data){
console.log(data)
})
// 3s后打印'失败'
Promise 大家应该都用过,ajax 库就是利用 Promise封装的;作用主要是解决地狱回调问题.
Promise.all()
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.all([p1, p2, p3])
上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成resolved,p的状态才会变成resolved,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被Rejected,p的状态就变成Rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
1.2 使用1.2.1.方法一
Promise.race()
与Promise.all()类似,不过是只要有一个Promise实例先改变了状态,p的状态就是它的状态,传递给回调函数的结果也是它的结果。所以很形象地叫做赛跑。
new Promise((resolve,reject)={ resolve('这是第一个 resolve 值')}).then((data)={ console.log(data) //会打印'这是第一个 resolve 值'}).catch(()={})new Promise((resolve,reject)={ reject('这是第一个 reject 值')}).then((data)={ console.log(data)}).catch((data)={ console.log(data) //会打印'这是第一个 reject 值'})
Promise.resolve()和Promise.reject()
有时需要将现有对象转为Promise对象,可以使用这两个方法。
1.2.2.方法二(静态方法)
Generator(生成器)
Promise.resolve('这是第二个 resolve 值').then((data)={ console.log(data) // 会打印'这是第二个 resolve 值'})Promise.reject('这是第二个 reject 值').then((data)={ console.log(data)}).catch(data={ console.log(data) //这是第二个 reject 值})
是什么
生成器本质上是一种特殊的迭代器(参见本文章系列二之Iterator)。ES6里的迭代器并不是一种新的语法或者是新的内置对象(构造函数),而是一种协议 (protocol)。所有遵循了这个协议的对象都可以称之为迭代器对象。生成器对象由生成器函数返回并且遵守了迭代器协议。具体参见MDN。
1.2.3.方法三(多个 Promise并行执行异步操作)
怎么用
表示多个 Promise 都进入到 FulFilled 或者 Rejected 就会执行
执行过程
生成器函数的语法为function*,在其函数体内部可以使用yield和yield*关键字。
function* gen(x){
console.log(1)
var y = yield x + 2
console.log(2)
return y
}
var g = gen(1)
当我们像上面那样调用生成器函数时,会发现并没有输出。这就是生成器函数与普通函数的不同,它可以交出函数的执行权(即暂停执行)。yield表达式就是暂停标志。
之前提到了生成器对象遵循迭代器协议,所以其实可以通过next方法执行。执行结果也是一个包含value和done属性的对象。
遍历器对象的next方法的运行逻辑如下。
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行。
g.next()
// 1
// { value: 3, done: false }
g.next()
// 2
// { value: undefined, done: true }
const pOne = new Promise((resolve, reject) = { resolve(1);});const pTwo = new Promise((resolve, reject) = { resolve(2);});const pThree = new Promise((resolve, reject) = { resolve(3);});Promise.all([pOne, pTwo, pThree]).then(data = { console.log(data); // [1, 2, 3] 正常执行完毕会执行这个,结果顺序和promise实例数组顺序是一致的}, err = { console.log(err); // 任意一个报错信息});
for...of遍历
生成器部署了迭代器接口,因此可以用for...of来遍历,不用调用next方法
function *foo() {
yield 1
yield 2
yield 3
return 4
}
for (let v of foo()) {
console.log(v)
}
// 1
// 2
// 3
1.2.4.方法四(多个中一个正常执行)
yield*表达式
从语法角度看,如果yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield表达式。yield后面只能跟迭代器,yield*的功能是将迭代控制权交给后面的迭代器,达到递归迭代的目的
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
表示多个 Promise 只有一个进入到 FulFilled 或者 Rejected 就会执行
自动执行
下面是使用Generator函数执行一个真实的异步任务的例子:
var fetch = require('node-fetch')
function* gen () {
var url = 'https://api.github.com/users/github'
var result = yield fetch(url)
console.log(result.bio)
}
上面代码中,Generator函数封装了一个异步操作,该操作先读取一个远程接口,然后从JSON格式的数据解析信息。这段代码非常像同步操作,除了加上了yield命令。
执行这段代码的方法如下
var g = gen()
var result = g.next()
result
.value
.then(function (data) {
return data.json()
})
.then(function (data) {
g.next(data)
})
上面代码中,首先执行Generator函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。由于Fetch模块返回的是一个Promise对象,因此要用then方法调用下一个next方法。
可以看到,虽然Generator函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。
那么如何自动化异步任务的流程管理呢?
Generator函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。
两种方法可以做到这一点。
回调函数。将异步操作包装成Thunk函数,在回调函数里面交回执行权。
Promise对象。将异步操作包装成Promise对象,用then方法交回执行权。
const pOne = new Promise((resolve, reject) = { resolve(1);});const pTwo = new Promise((resolve, reject) = { resolve(2);});const pThree = new Promise((resolve, reject) = { // resolve(3);});Promise.race([pOne, pTwo, pThree]).then(data = { console.log(data); // 1 只要碰到FulFilled 或者 Rejected就会停止执行}, err = { console.log(err); // 任意一个报错信息});
Thunk函数
本节很简略,可能会看不太明白,请参考Thunk 函数的含义和用法
Thunk函数的含义:编译器的"传名调用"实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做Thunk函数。
JavaScript语言是传值调用,它的Thunk函数含义有所不同。在JavaScript语言中,Thunk函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。
任何函数,只要参数有回调函数,就能写成Thunk函数的形式,可以通过一个Thunk函数转换器来转换。
Thunk函数真正的威力,在于可以自动执行Generator函数。我们可以实现一个基于Thunk函数的Generator执行器,然后直接把Generator函数传入这个执行器即可。
function run(fn) {
var gen = fn()
function next(err, data) {
var result = gen.next(data)
if (result.done) return
result.value(next)
}
next()
}
function* g() {
// ...
}
run(g)
Thunk函数并不是Generator函数自动执行的唯一方案。因为自动执行的关键是,必须有一种机制,自动控制Generator函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise对象也可以做到这一点。
1.3 作用分析1.3.1 参数和状态
基于Promise对象的自动执行
首先,将方法包装成一个Promise对象(fs是nodejs的一个内置模块)。
var fs = require('fs')
var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function (error, data) {
if (error) reject(error)
resolve(data)
})
})
}
var gen = function* () {
var f1 = yield readFile('/etc/fstab')
var f2 = yield readFile('/etc/shells')
console.log(f1.toString())
console.log(f2.toString())
}
然后,手动执行上面的Generator函数。
var g = gen()
g.next().value.then(function (data) {
g.next(data).value.then(function (data) {
g.next(data)
})
})
观察上面的执行过程,其实是在递归调用,我们可以用一个函数来实现:
function run(gen){
var g = gen()
function next(data){
var result = g.next(data)
if (result.done) return result.value
result.value.then(function(data){
next(data)
})
}
next()
}
run(gen)
上面代码中,只要Generator函数还没执行到最后一步,next函数就调用自身,以此实现自动执行。
1.Promise接受一个函数handle作为参数,handle包括resolve和reject两个是函数的参数2.Promise 相当于一个状态机,有三种状态:pending,fulfilled,reject,初始状态为 pending3.调用 resolve,状态由pending = fulfilled4.调用reject,会由pending = rejected5.改变之后不会变化
co模块
co模块是nodejs社区著名的TJ大神写的一个小工具,用于Generator函数的自动执行。
下面是一个Generator函数,用于依次读取两个文件
var gen = function* () {
var f1 = yield readFile('/etc/fstab')
var f2 = yield readFile('/etc/shells')
console.log(f1.toString())
console.log(f2.toString())
}
var co = require('co')
co(gen)
co模块可以让你不用编写Generator函数的执行器。Generator函数只要传入co函数,就会自动执行。co函数返回一个Promise对象,因此可以用then方法添加回调函数。
co(gen).then(function () {
console.log('Generator 函数执行完成')
})
co模块的原理:其实就是将两种自动执行器(Thunk函数和Promise对象),包装成一个模块。使用co的前提条件是,Generator函数的yield命令后面,只能是Thunk函数或Promise对象。如果数组或对象的成员,全部都是Promise对象,也可以使用co(co v4.0版以后,yield命令后面只能是Promise对象,不再支持Thunk函数)。
1.3.2 then 方法
async(异步)函数
1.接受两个参数,onFulfilled和onRejected可选的函数
是什么
async函数属于ES7。目前,它仍处于提案阶段,但是转码器Babel和regenerator都已经支持。async函数可以说是目前异步操作最好的解决方案,是对Generator函数的升级和改进。
2.不是函数必须被忽略
怎么用
1)语法
async函数声明定义了异步函数,它会返回一个AsyncFunction对象。和普通函数一样,你也可以定义一个异步函数表达式。
调用异步函数时会返回一个promise对象。当这个异步函数成功返回一个值时,将会使用promise的resolve方法来处理这个返回值,当异步函数抛出的是异常或者非法值时,将会使用promise的reject方法来处理这个异常值。
异步函数可能会包括await表达式,这将会使异步函数暂停执行并等待promise解析传值后,继续执行异步函数并返回解析值。
注意:await只能用在async函数中。
前面依次读取两个文件的代码写成async函数如下:
var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab')
var f2 = await readFile('/etc/shells')
console.log(f1.toString())
console.log(f2.toString())
}
async函数将Generator函数的星号(*)替换成了async,将yield改为了await。
2)async函数的改进
async函数对Generator函数的改进,体现在以下三点。
(1)内置执行器。Generator函数的执行必须靠执行器,所以才有了co函数库,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
var result = asyncReadFile()
(2)更好的语义。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。co函数库约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以跟Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
3)基本用法
同Generator函数一样,async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
function resolveAfter2Seconds (x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x)
}, 2000)
})
}
async function add1 (x) {
var a = resolveAfter2Seconds(20)
var b = resolveAfter2Seconds(30)
return x + await a + await b
}
add1(10).then(v => {
console.log(v)
})
// 2s后打印60
async function add2 (x) {
var a = await resolveAfter2Seconds(20)
var b = await resolveAfter2Seconds(30)
return x + a + b
}
add2(10).then(v => {
console.log(v)
})
// 4s后打印60
4)捕获错误
可以使用.catch回调捕获错误,也可以使用传统的try...catch。
async function myFunction () {
try {
await somethingThatReturnsAPromise()
} catch (err) {
console.log(err)
}
}
// 另一种写法
async function myFunction () {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err)
}
}
5)并发的异步操作
let foo = await getFoo()
let bar = await getBar()
多个await命令后面的异步操作会按顺序完成。如果不存在继发关系,最好让它们同时触发。上面的代码只有getFoo完成,才会去执行getBar,这样会比较耗时。如果这两个是独立的异步操作,完全可以让它们同时触发。
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()])
// 写法二
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise
3.onFullfilled:A.当 promise 状态变为成功时必须被调用,其第一个参数为 promise 成功状态传入的值( resolve 执行时传入的值;B.在 promise 状态改变前其不可被调用C.其调用次数不可超过一次
4.onRejected:作用和onFullfilled类似,只不过是promise失败调用
5.then方法可以链式调用A.每次返回一个新的PromiseB.执行规则和错误捕获:then的返回值如果是非Promise直接作为下一个新Promise参数,如果是Promise会等Promise执行
// 返回非Promiselet promise1 = new Promise((resolve, reject) = { setTimeout(() = { resolve() }, 1000)})promise2 = promise1.then(res = { // 返回一个普通值 return '这里返回一个普通值'})promise2.then(res = { console.log(res) //1秒后打印出:这里返回一个普通值})// 返回Promiselet promise1 = new Promise((resolve, reject) = { setTimeout(() = { resolve() }, 1000)})promise2 = promise1.then(res = { // 返回一个Promise对象 return new Promise((resolve, reject) = { setTimeout(() = { resolve('这里返回一个Promise') }, 2000) })})promise2.then(res = { console.log(res) //3秒后打印出:这里返回一个Promise})
C. onFulfilled 或者onRejected 抛出一个异常 e ,则 promise2 必须变为失败(Rejected),并返回失败的值 e
let promise1 = new Promise((resolve, reject) = { setTimeout(() = { resolve('success') }, 1000)})promise2 = promise1.then(res = { throw new Error('这里抛出一个异常e')})promise2.then(res = { console.log(res)}, err = { console.log(err) //1秒后打印出:这里抛出一个异常e})
D.onFulfilled 不是函数且 promise1 状态为成功(Fulfilled), promise2 必须变为成功(Fulfilled)并返回 promise1 成功的值
let promise1 = new Promise((resolve, reject) = { setTimeout(() = { resolve('success') }, 1000)})promise2 = promise1.then('这里的onFulfilled本来是一个函数,但现在不是')promise2.then(res = { console.log(res) // 1秒后打印出:success}, err = { console.log(err)})
E.onRejected 不是函数且 promise1 状态为失败(Rejected),promise2必须变为失败(Rejected) 并返回 promise1 失败的值
let promise1 = new Promise((resolve, reject) = { setTimeout(() = { reject('fail') }, 1000)})promise2 = promise1.then(res = res, '这里的onRejected本来是一个函数,但现在不是')promise2.then(res = { console.log(res)}, err = { console.log(err) // 1秒后打印出:fail})
F.resolve和reject结束一个Promise的调用G.catch方法,捕获异常其实复杂的是在then的异常处理上,不过不用急,边看边理解
1.3.3 方法
1.静态resolve方法参照1.3.1用法2
2.静态reject方法参照1.3.1用法2
3.静态all方法参照1.3.1用法3
4.静态race方法参照1.3.1用法4
5.自定义done方法Promise 对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise内部的错误不会冒泡到全局)因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误
Promise.prototype.done = function (onFulfilled, onRejected) { this .then(onFulfilled, onRejected) .catch(function (reason) { // 抛出一个全局错误 setTimeout(() = { throw reason }, 0) }) }
6.自定义finally方法finally方法用于指定不管Promise对象最后状态如何,都会执行的操作它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行
Promise.prototype.finally = function (callback) { let P = this.constructor return this.then( value = P.resolve(callback()).then(() = value), reason = P.resolve(callback()).then(() = { throw reason }) ) }
1.4 PolyFill1.4.1 初级版
class MyPromise { constructor (handle) { // 判断handle函数与否 if (typeof handle!=='function') { throw new Error('MyPromise must accept a function as a parameter') } // 添加状态 this._status = 'PENDING' // 添加状态 this._value = undefined // 执行handle try { handle(this._resolve.bind(this), this._reject.bind(this)) } catch (err) { this._reject(err) } } // 添加resovle时执行的函数 _resolve (val) { if (this._status !== 'PENDING') return this._status = 'FULFILLED' this._value = val } // 添加reject时执行的函数 _reject (err) { if (this._status !== 'PENDING') return this._status = 'REJECTED' this._value = err }}
回顾一下,初级版实现了1,2,3这三点功能,怎么样还是so-easy吧!
1.4.2 中级版
1.由于 then 方法支持多次调用,我们可以维护两个数组,将每次 then 方法注册时的回调函数添加到数组中,等待执行在初级的基础上加入成功回调函数队列和失败回调队列和then方法
this._fulfilledQueues = []this._rejectedQueues = []
2.then方法将
then (onFulfilled, onRejected) { const { _value, _status } = this switch (_status) { // 当状态为pending时,将then方法回调函数加入执行队列等待执行 case 'PENDING': this._fulfilledQueues.push(onFulfilled) this._rejectedQueues.push(onRejected) break // 当状态已经改变时,立即执行对应的回调函数 case 'FULFILLED': onFulfilled(_value) break case 'REJECTED': onRejected(_value) break } // 返回一个新的Promise对象 return new MyPromise((onFulfilledNext, onRejectedNext) = { })}
3.then方法规则完善
// 添加then方法then (onFulfilled, onRejected) { const { _value, _status } = this // 返回一个新的Promise对象 return new MyPromise((onFulfilledNext, onRejectedNext) = { // 封装一个成功时执行的函数 let fulfilled = value = { try { if (typeof onFulfilled!=='function') { onFulfilledNext(value) } else { let res = onFulfilled(value); if (res instanceof MyPromise) { // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调 res.then(onFulfilledNext, onRejectedNext) } else { //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数 onFulfilledNext(res) } } } catch (err) { // 如果函数执行出错,新的Promise对象的状态为失败 onRejectedNext(err) } } // 封装一个失败时执行的函数 let rejected = error = { try { if (typeof onRejected!=='function') { onRejectedNext(error) } else { let res = onRejected(error); if (res instanceof MyPromise) { // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调 res.then(onFulfilledNext, onRejectedNext) } else { //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数 onFulfilledNext(res) } } } catch (err) { // 如果函数执行出错,新的Promise对象的状态为失败 onRejectedNext(err) } } switch (_status) { // 当状态为pending时,将then方法回调函数加入执行队列等待执行 case 'PENDING': this._fulfilledQueues.push(fulfilled) this._rejectedQueues.push(rejected) break // 当状态已经改变时,立即执行对应的回调函数 case 'FULFILLED': fulfilled(_value) break case 'REJECTED': rejected(_value) break } })}
4.当 resolve 或 reject 方法执行时,我们依次提取成功或失败任务队列当中的函数开始执行,并清空队列,从而实现 then 方法的多次调用
// 添加resovle时执行的函数_resolve (val) { if (this._status !== PENDING) return // 依次执行成功队列中的函数,并清空队列 const run = () = { this._status = FULFILLED this._value = val let cb; while (cb = this._fulfilledQueues.shift()) { cb(val) } } // 为了支持同步的Promise,这里采用异步调用 setTimeout(() = run(), 0)}// 添加reject时执行的函数_reject (err) { if (this._status !== PENDING) return // 依次执行失败队列中的函数,并清空队列 const run = () = { this._status = REJECTED this._value = err let cb; while (cb = this._rejectedQueues.shift()) { cb(err) } } // 为了支持同步的Promise,这里采用异步调用 setTimeout(run, 0)}
5.当 resolve 方法传入的参数为一个 Promise 对象时,则该 Promise 对象状态决定当前 Promise 对象的状态
// 添加resovle时执行的函数_resolve (val) { const run = () = { if (this._status !== PENDING) return this._status = FULFILLED // 依次执行成功队列中的函数,并清空队列 const runFulfilled = (value) = { let cb; while (cb = this._fulfilledQueues.shift()) { cb(value) } } // 依次执行失败队列中的函数,并清空队列 const runRejected = (error) = { let cb; while (cb = this._rejectedQueues.shift()) { cb(error) } } /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后, 当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态 */ if (val instanceof MyPromise) { val.then(value = { this._value = value runFulfilled(value) }, err = { this._value = err runRejected(err) }) } else { this._value = val runFulfilled(val) } } // 为了支持同步的Promise,这里采用异步调用 setTimeout(run, 0)}
6.catch方法
// 添加catch方法catch (onRejected) { return this.then(undefined, onRejected)}
1.4.3 高级版
1.静态 resolve 方法
// 添加静态resolve方法static resolve (value) { // 如果参数是MyPromise实例,直接返回这个实例 if (value instanceof MyPromise) return value return new MyPromise(resolve = resolve(value))}
2.静态 reject 方法
// 添加静态reject方法static reject (value) { return new MyPromise((resolve ,reject) = reject(value))}
3.静态 all 方法
// 添加静态all方法static all (list) { return new MyPromise((resolve, reject) = { /** * 返回值的集合 */ let values = [] let count = 0 for (let [i, p] of list.entries()) { // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve this.resolve(p).then(res = { values[i] = res count++ // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled if (count === list.length) resolve(values) }, err = { // 有一个被rejected时返回的MyPromise状态就变成rejected reject(err) }) } })}
4.静态 race 方法
// 添加静态race方法static race (list) { return new MyPromise((resolve, reject) = { for (let p of list) { // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变 this.resolve(p).then(res = { resolve(res) }, err = { reject(err) }) } })}
5.done方法作用:不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise 内部的错误不会冒泡到全局);处于回调链的尾端,保证抛出任何可能出现的错误目前 Promise 上还没有 done,我们可以自定义一个
Promise.prototype.done = function (onFulfilled, onRejected) { console.log('done') this.then(onFulfilled, onRejected) .catch((reason)= { // 抛出一个全局错误 setTimeout(() = { throw reason }, 0) }) }Promise.resolve('这是静态方法的第一个 resolve 值').then(()={ return '这是静态方法的第二个 resolve 值'}).then(()={ throw('这是静态方法的第一个 reject 值') return '这是静态方法的第二个 resolve 值'}).done()
6.finally方法作用:不管 Promise 对象最后状态如何,都会执行的操作与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行
finally (cb) { return this.then( value = MyPromise.resolve(cb()).then(() = value), reason = MyPromise.resolve(cb()).then(() = { throw reason }) );};
7.完整代码请戳,源码地址,欢迎 star!
本文由10bet发布于Web前端,转载请注明出处:如何写出一个惊艳面试官的 Promise
关键词: