1.高级 WEB 面试会让你手写一个Promise,Generator 的 PolyFill(一段代码);
2.在写以前咱们简单回顾下他们的做用;
3.手写模块见PolyFill.vue
源码地址请戳,原创码字不易,欢迎 star react
若是以为看文章太啰嗦,能够直接 git clone ,直接看代码git
Promise 你们应该都用过,ajax 库就是利用 Promise封装的;
做用主要是解决地狱回调问题.github
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('这是第二个 resolve 值').then((data)=>{ console.log(data) // 会打印'这是第二个 resolve 值' }) Promise.reject('这是第二个 reject 值').then((data)=>{ console.log(data) }).catch(data=>{ console.log(data) //这是第二个 reject 值 })
表示多个 Promise 都进入到 FulFilled 或者 Rejected 就会执行面试
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); // 任意一个报错信息 });
表示多个 Promise 只有一个进入到 FulFilled 或者 Rejected 就会执行ajax
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); // 任意一个报错信息 });
1.Promise接受一个函数handle做为参数,handle包括resolve和reject两个是函数的参数
2.Promise 至关于一个状态机,有三种状态:pending,fulfilled,reject,初始状态为 pending
3.调用 resolve,状态由pending => fulfilled
4.调用reject,会由pending => rejected
5.改变以后不会变化数组
1.接受两个参数,onFulfilled和onRejected可选的函数 promise
2.不是函数必须被忽略 koa
3.onFullfilled:
A.当 promise 状态变为成功时必须被调用,其第一个参数为 promise 成功状态传入的值( resolve 执行时传入的值;
B.在 promise 状态改变前其不可被调用
C.其调用次数不可超过一次 异步
4.onRejected:做用和onFullfilled相似,只不过是promise失败调用
5.then方法能够链式调用
A.每次返回一个新的Promise
B.执行规则和错误捕获:then的返回值若是是非Promise直接做为下一个新Promise参数,若是是Promise会等Promise执行
// 返回非Promise let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) promise2 = promise1.then(res => { // 返回一个普通值 return '这里返回一个普通值' }) promise2.then(res => { console.log(res) //1秒后打印出:这里返回一个普通值 }) // 返回Promise let 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.静态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 }) ) }
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.因为 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.静态 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!
1.Generator能够理解为一个状态机,内部封装了不少状态,同时返回一个迭代器Iterator对象;
2.迭代器Iterator对象:定义标准方式产生一个有限或无限序列值,迭代器有next()对象;
3.屡次返回能够被 next屡次调用,最大特色是能够控制执行顺序;
2.是一种特殊的函数
function* gen(x){ const y = yield x + 6; return y; } // yield 若是用在另一个表达式中,要放在()里面 // 像上面若是是在=右边就不用加() function* genOne(x){ const y = `这是第一个 yield 执行:${yield x + 1}`; return y; }
整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操做须要暂停的地方,都用 yield 语句注明
1.普通执行
const g = gen(1); //执行 Generator 会返回一个Object,而不是像普通函数返回return 后面的值 g.next() // { value: 7, done: false } //调用指针的 next 方法,会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式或return语句暂停,也就是执行yield 这一行 // 执行完成会返回一个 Object, // value 就是执行 yield 后面的值,done 表示函数是否执行完毕 g.next() // { value: undefined, done: true } // 由于最后一行 return y 被执行完成,因此done 为 true
2.next 方法传参数
const g = gen(1); g.next() // { value: 7, done: false } g.next(2) // { value: 2, done: true } // next 的参数是做为上个阶段异步任务的返回结果 3.嵌套执行 必须用到yield*关键字
function* genTwo(x){
yield* gen(1)
yield* genOne(1)
const y = 这是第 二个 yield 执行:${yield x + 2}
;
return y;
}
const iterator=genTwo(1)
// 由于 Generator 函数运行时生成的是一个 Iterator 对象,因此能够直接使用 for...of 循环遍历
for(let value of iterator) {
console.log(value)
}
相同点:
1.都能返回语句后面的那个表达式的值
2.均可以暂停函数执行
区别:
一个函数能够有多个 yield,可是只能有一个 return
yield 有位置记忆功能,return 没有
抛出错误,能够被try...catch...捕捉到
g.throw('这是抛出的一个错误'); // 这是抛出的一个错误
// 要求:函数valOne,valTwo,valThree 以此执行 function* someTask(){ try{ const valOne=yield 1 const valTwo=yield 2 const valThree=yield 3 }catch(e){ } } scheduler(someTask()); function scheduler(task) { const taskObj = task.next(task.value); console.log(taskObj) // 若是Generator函数未结束,就继续调用 if (!taskObj.done) { task.value = taskObj.value scheduler(task); } }
原理图
实现一个迭代器(Iterator)
// 源码实现 function createIterator(items) { var i = 0 return { next: function() { var done = (i >= items.length) var value = !done ? items[i++] : undefined return { done: done, value: value } } } } // 应用 const iterator = createIterator([1, 2, 3]) console.log(iterator.next()) // {value: 1, done: false} console.log(iterator.next()) // {value: 2, done: false} console.log(iterator.next()) // {value: 3, done: false} console.log(iterator.next()) // {value: undefined, done: true}
实现可迭代(Iterable)
1.能够经过 for...of...遍历的对象,即原型链上有Symbol.iterator属性;
2.Symbol.iterator:返回一个对象的无参函数,被返回对象符合迭代器协议;
3.for...of接受一个可迭代对象(Iterable),或者能强制转换/包装成一个可迭代对象的值(如’abc’),遍历时,for...of会获取可迭代对象的'Symbol.iterator',对该迭代器逐次调用next(),直到迭代器返回对象的done属性为true时,遍历结束,不对该value处理;
const a = ['a', 'b', 'c', 'd', 'e'] for (let val of a) { console.log(val) } // 'a' 'b' 'c' 'd' 'e' // 等价于 const a = ["a", "b", "c", "d", "e"] for (let val, ret, it = a[Symbol.iterator](); (ret = it.next()) && !ret.done; ) { let = ret.value console.log(val) } // "a" "b" "c" "d" "e"
4.yield* 可返回一个 Iterable对象;
5.源码改造
function createIterator(items) { let i = 0 return { next: function () { let done = (i >= items.length) let value = !done ? items[i++] : undefined return { done: done, value: value } } [Symbol.iterator]: function () { return this } } } let iterator = createIterator([1, 2, 3]) ...iterator // 1, 2, 3
1.for...of...接收可迭代对象,能强制转换或包装可迭代对象的值;
2.遍历时,for...of会获取可迭代对象的'Symbol.iterator',对该迭代器逐次调用next(),直到迭代器返回对象的done属性为true时,遍历结束,不对该value处理;
3.因此能够利用 for...of...封装到原型链上.
Object.prototype[Symbol.iterator] = function* () { for (const key in this) { if (this.hasOwnProperty(key)) { yield [key, this[key]] } } }
1.async 函数返回的是一个 Promise 对象
在函数中 return 一个直接量,async 会把这个直接量经过 Promise.resolve() 封装成 Promise 对象
async function testAsync() { return "hello async"; } const result = testAsync(); console.log(result); //Promise 对象
2.async和then
async返回一个Promise,因此能够经过then获取值
testAsync().then(v => { console.log(v); // 输出 hello async });
因此async里面的函数会立刻执行,这个就相似Generator的‘*’
1.await后面能够是Promise对象或其余表达式
function getSomething() { return "something"; } async function testAsync() { return Promise.resolve("hello async"); } async function test() { const v1 = await getSomething(); const v2 = await testAsync(); console.log(v1, v2); //something 和 hello async } test();
2.await后面不是Promise对象,直接执行
3.await后面是Promise对象会阻塞后面的代码,Promise 对象 resolve,而后获得 resolve 的值,做为 await 表达式的运算结果
4.因此这就是await必须用在async的缘由,async恰好返回一个Promise对象,能够异步执行阻塞
1.主要是处理Promise的链式回调或函数的地狱回调
回到Generator中要求函数valOne,valTwo,valThree函数依次执行
function valOne(){} function valTwo(){} function valThree(){} async ()=>{ await valOne() await valTwo() await valThree() }
2.处理异常
try...catch...
或者await .catch()
1.async是内置执行器,Generator 函数的执行必须依靠执行器,无需手动执行next()
2.更广的适用性。co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而await后面能够是任意表达式,都会返回一个Promise对象
// Thunk函数:是能将执行结果传入回调函数,并将该回调函数返回的函数 function f(m) { return m * 2; } f(x + 5); // 等同于 var thunk = function () { return x + 5; }; function f(thunk) { return thunk() * 2; }
3.返回Promise,而Generator返回 Iterator
4.async 函数就是 Generator 函数的语法糖
async就至关于Generator的*,await至关于yield,用法有不少类似之处
实现执行器两种方式:
回调函数(Thunk 函数)
Promise 对象
https://juejin.im/post/5dc28e...
async function fn(args) { // ... } // 等价于 function fn(args) { return spawn(function* () { // ... }); } function spawn(gen){ let g = gen(); function next(data){ let result = g.next(data); if (result.done) return result.value; result.value.then(function(data){ next(data); }); } next(); }
function spawn(genF) { //spawn函数就是自动执行器,跟简单版的思路是同样的,多了Promise和容错处理 return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }
1.代码对比:
场景:假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。若是当中有一个动画出错,就再也不往下执行,返回上一个成功执行的动画的返回值。
A.Promise
function chainAnimationsPromise(elem, animations) { // 变量ret用来保存上一个动画的返回值 let ret = null; // 新建一个空的Promise let p = Promise.resolve(); // 使用then方法,添加全部动画 for(let anim of animations) { p = p.then(function(val) { ret = val; return anim(elem); }); } // 返回一个部署了错误捕捉机制的Promise return p.catch(function(e) { /* 忽略错误,继续执行 */ }).then(function() { return ret; }); }
B.Generator
function chainAnimationsGenerator(elem, animations) { return spawn(function*() { let ret = null; try { for(let anim of animations) { ret = yield anim(elem); } } catch(e) { /* 忽略错误,继续执行 */ } return ret; }); }
C.async
async function chainAnimationsAsync(elem, animations) { let ret = null; try { for(let anim of animations) { ret = await anim(elem); } } catch(e) { /* 忽略错误,继续执行 */ } return ret; }
对比能够看出 async...await...代码更优雅
async 和 await 是在 Generator 的基础上封装了自执行函数和一些特性;
具体对比见没个 API 的 PolyFill
来道头条的面试
console.log('script start') async function async1() { await async2() console.log('async1 end') } async function async2() { console.log('async2 end') } async1() setTimeout(function() { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) .then(function() { console.log('promise2') }) console.log('script end') // 旧版 Chrome 打印 // script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout // 新版 Chrome 打印 // script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout // 这里面其余的顺序除了async1 end , promise1 , promise2 这几个顺序有点争议,其余应该没有什么问题 // 新版是由于V8 团队将最新的规范进行了修改,await变得更快了,这道题细节分析再也不赘述,上面原理都有讲到
原创码字不易,欢迎 star!