如何写出一个惊艳面试官的 Promise

更新日期: 2019-11-17阅读: 2.2k标签: 面试

前言

1.高级 WEB 面试会让你手写一个Promise,Generator 的 PolyFill(一段代码); 
2.在写之前我们简单回顾下他们的作用; 
3.手写模块见PolyFill.


源码

源码地址请戳,原创码字不易,欢迎 star

如果觉得看文章太啰嗦,可以直接 git clone ,直接看代码


1.Promise

1.1 作用

Promise 大家应该都用过,ajax 库就是利用 Promise封装的; 
作用主要是解决地狱回调问题.

1.2 使用

1.2.1.方法一

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 值'
})

1.2.2.方法二(静态方法)

Promise.resolve('这是第二个 resolve 值').then((data)=>{
  console.log(data) // 会打印'这是第二个 resolve 值'
})

Promise.reject('这是第二个 reject 值').then((data)=>{
  console.log(data)
}).catch(data=>{
  console.log(data) //这是第二个 reject 值
})

1.2.3.方法三(多个 Promise并行执行异步操作)

表示多个 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); // 任意一个报错信息
});

1.2.4.方法四(多个中一个正常执行)

表示多个 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.race([pOne, pTwo, pThree]).then(data => { 
    console.log(data); // 1 只要碰到FulFilled 或者 Rejected就会停止执行
}, err => {
    console.log(err); // 任意一个报错信息
});

1.3 作用分析

1.3.1 参数和状态

1.Promise接受一个函数handle作为参数,handle包括resolve和reject两个是函数的参数 
2.Promise 相当于一个状态机,有三种状态:pending,fulfilled,reject,初始状态为 pending 
3.调用 resolve,状态由pending => fulfilled 
4.调用reject,会由pending => rejected 
5.改变之后不会变化

1.3.2 then 方法

1.接受两个参数,onFulfilled和onRejected可选的函数

2.不是函数必须被忽略

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.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 PolyFill

1.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!


2.Generator

2.1 定义

1.Generator可以理解为一个状态机,内部封装了很多状态,同时返回一个迭代器Iterator对象; 
2.迭代器Iterator对象:定义标准方式产生一个有限或无限序列值,迭代器有next()对象; 
3.多次返回可以被 next多次调用,最大特点是可以控制执行顺序;

2.2 声明方法

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 语句注明

2.3 执行

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)
}

2.4 yield和 return 的区别

相同点:
1.都能返回语句后面的那个表达式的值 
2.都可以暂停函数执行 
区别: 
一个函数可以有多个 yield,但是只能有一个 return 
yield 有位置记忆功能,return 没有

2.5 throw

抛出错误,可以被try...catch...捕捉到

g.throw('这是抛出的一个错误');
// 这是抛出的一个错误

2.6 应用

// 要求:函数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);
  }
}

2.7 PolyFill

原理图

2.7.1 初级版

实现一个迭代器(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}

2.7.2 中级版

实现可迭代(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

2.7.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]]
        }
    }
}


3.async 和 await

3.1 async作用

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的‘*’

3.2 await作用

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对象,可以异步执行阻塞

3.3 async和await结合作用

1.主要是处理Promise的链式回调或函数的地狱回调
回到Generator中要求函数valOne,valTwo,valThree函数依次执行

function valOne(){}
function valTwo(){}
function valThree(){}

async ()=>{
  await valOne()
  await valTwo()
  await valThree()
}

2.处理异常 
try...catch... 
或者await .catch()

3.4 和Generator的区别

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,用法有很多相似之处

3.5 执行器PolyFill

实现执行器两种方式: 
回调函数(Thunk 函数) 
Promise 对象

3.5.1 初级版

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();
}

3.5.2 高级版

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); });
  });
}


4.Promise,Generator,async和await对比

4.1 代码

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...代码更优雅

4.2 原理

async 和 await 是在 Generator 的基础上封装了自执行函数和一些特性; 
具体对比见没个 api 的 PolyFill

4.3 执行顺序

来道头条的面试

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!

链接: https://fly63.com/article/detial/6887

Web前端年后跳槽面试复习指南

很多童鞋可能年后有自己的一些计划,比如换份工作环境,比如对职业目标有了新的打算。当然面试这一关不得不过,大概又不可能系统性的复习,这里罗列一些 重点 面试的知识点和文章,

前端面试之webpack面试常见问题

什么是webpack和grunt和gulp有什么不同?什么是bundle,什么是chunk,什么是module?什么是Loader?什么是Plugin?如何可以自动生成webpack配置?webpack-dev-server和http服务器如nginx有什么区别?

每个 JavaScript 工程师都应当知道的 10 个面试题

多问问应聘者高层次的知识点,如果能讲清楚这些概念,就说明即使应聘者没怎么接触过 JavaScript,也能够在短短几个星期之内就把语言细节和语法之类的东西弄清楚。

37个JavaScript基本面试问题和解答

面试比棘手的技术问题要多,这篇文章整理了37个JavaScript基本面试问题和解答,这些仅仅是作为指导。希望对前端开发的你有所帮助!

React常见面试题

React常见面试题:React中调用setState之后发生了什么事情?React中Element与Component的区别?优先选择使用ClassComponent而不是FunctionalComponent?React中的refs属性的作用是什么?React中keys的作用是什么?

有趣的Js面试题_如何让 (a == 1 && a == 2 && a == 3) 返回 true

题目大意为:JS 环境下,如何让 a == 1 && a == 2 && a == 3 这个表达式返回 true ?这道题目乍看之下似乎不太可能,因为在正常情况下,一个变量的值如果没有手动修改,在一个表达式中是不会变化的。

js练习笔记:10道JavaScript题目

10道JavaScript题目:累加函数addNum、实现一个Person类、实现一个arrMerge 函数、实现一个toCamelStyle函数、setTimeout实现重复调用、实现一个bind函数、实现一个Utils模块、输出一个对象自身的属性

vue菜鸟从业记:没准备好的面试,那叫尬聊

面试开场白总缺少不了自我介绍,一方面是面试官想听听你对自己的介绍,顺便有时间看看简历上的描述,是否与口述一致。另一方面就是看看你简历上做过什么项目,用到了哪些技术栈,一会儿好提问你。

毕业一年左右的前端妹子面试总结

把面试当做学习,这个过程你会收益很大。前端知识很杂,可能实际工作中用到的技术,像框架都是跟着公司的要求走的,像我最近也在看React啦,Vue和React都对比着再学习

vue面试时需要准备的知识点

vue上手可以说是比较轻松而且简单,如果你用过angular,react,你也会很喜欢vue。vue的核心思想依旧是:构建用户界面的渐进式框架,关注视图的变化。这也是为什么新建的文件是结构是template script style

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!