乐趣区

关于javascript:压箱底笔记Promise和Asyncawait的理解和使用

以前学习写的笔记,感觉还不错,当初收回来,心愿对你有帮忙。

1. 前置常识

1.1 区别实例对象与函数对象

实例对象:new 函数产生的对象, 称为实例对象, 简称为对象

函数对象:将函数作为对象应用时, 简称为函数对象

function Fn() {}
const fn = new Fn() // fn 为实例对象
Fn.bind({}) // Fn 为函数对象 

1.2 两种类型的回调函数

同步回调

  • 了解:立刻执行, 齐全执行完了才完结, 不会放入回调队列中
  • 例子: 数组遍历相干的回调函数 / Promise 的 excutor 函数

异步回调

  • 了解:不会立刻执行, 会放入回调队列中未来执行
  • 例子:定时器回调 / ajax 回调 / Promise 的胜利 | 失败的回调
const arr = [1, 2, 3]
arr.forEach(item => console.log(item)) // 同步回调, 不会放入回调队列, 而是立刻执行
console.log('forEatch() 之后')
setTimeout(() => { // 异步回调, 会放入回调队列, 所有同步执行完后才可能执行
  console.log('timout 回调')
}, 0)
console.log('setTimeout 之后')

1.3 JS 的 error 解决

谬误的类型

  • Error:所有谬误的父类型
  • ReferenceError:援用的变量不存在

    console.log(a) // ReferenceError: a is not defined
  • TypeError:数据类型不正确的谬误

    let b = null
    console.log(b.xxx) // TypeError: Cannot read property 'xxx' of null
  • RangeError:数据值不在其所容许的范畴内

    function fn() {fn()
    }
    fn() // RangeError: Maximum call stack size exceeded
  • SyntaxError:语法错误

    let c = """" // SyntaxError: Unexpected string

错误处理

  • 捕捉谬误:try … catch
  • 抛出谬误:throw error

error 对象的构造

  • message 属性:谬误相干信息
  • stack 属性:函数调用栈记录信息

2. Promise 是什么?

2.1 了解

形象表白:Promise 是 JS 中进行异步编程的新的解决方案(旧的是谁?=> 纯回调的模式)

具体表白:

  • 从语法上来说:Promise 是一个构造函数
  • 从性能上来说:Promise 对象用来封装一个异步操作并能够获取其后果

2.2 Promise 的状态扭转

Promise 的状态扭转只有这 2 种:

且一个 Promise 对象只能扭转一次,无论变成胜利还是失败,都会有一个后果数据,胜利的后果数据个别称为 value,失败的后果数据个别称为 reason

2.3 Promise 根本流程

2.4 Promise 的根本应用

示例,如果以后工夫是偶数就代表胜利,否则代表失败

// 1. 创立一个新的 Promise 对象
const p = new Promise((resolve, reject) => { // 执行器函数,同步执行
  // 2. 执行异步操作工作
  setTimeout(() => {const time = Date.now() // 如果以后工夫是偶数就代表胜利,否则代表失败
    // 3.1 如果胜利了,调用 resolve(value)
    if (time % 2 === 0) {resolve('胜利的数据,value =' + time)
    } else {// 3.2 如果失败了,调用 reject(reason)
        reject('失败的数据,reason =' + time)
    }
  }, 1000);
})

p.then(value => {
  // 承受失去胜利的 value 数据,专业术语:onResolved
  console.log('胜利的回调', value)
}, reason => {
  // 承受失去失败的 reason 数据,专业术语:onRejected
  console.log('失败的回调', reason)
})

3. 为什么要用 Promise?

3.1 指定回调函数的形式更加灵便

旧的:回调函数必须在启动异步工作前指定

// 胜利的回调函数
function successCallback(result) {console.log('解决胜利:' + result)
}

function failureCallback(error) {console.log('解决失败:' + error)
}

// 应用纯回调函数
createAudioFileSync(audioSettings, successCallback, failureCallback)

Promise:启动异步工作 => 返回 Promise 对象 => 给 Promise 对象绑定回调函数,甚至能够在异步工作完结后指定多个

// 应用 Promise
const promise = createAudioFileSync(audioSettings)
setTimeout(() => {promise.then(successCallback, failureCallback)
}, 3000);

3.2 反对链式调用,能够解决回调天堂问题

什么是回调天堂?回调函数嵌套调用,内部回调函数异步执行的后果是嵌套的回掉执行条件,代码是程度向右扩大

// 回调天堂
doSomething(function(result) {doSomethingElse(result, function(newResult) {doThirdThing(newResult, function(finalResult) {console.log('Got the final result:' + finalResult)
    }, failureCallback)
  }, failureCallback)
}, 

回调天堂的毛病:不便浏览,不便于异样解决

解决方案:Promise 链式调用,代码程度向下扩大

doSomething().then(function(result) {return doSomethingElse(result)
})
.then(function(newResult) {return doThirdThing(newResult)
})
.then(function(finalResult) {console.log('Got the final result:' + finalResult)
})
.catch(failureCallback)

终极解决方案:async/await,用同步的写法解决异步的操作

async function request() {
  try {const result = await doSomething()
    const newResult = await doSomethingElse(result)
    const finalResult = await doThirdThing(newResult)
    console.log('Got the final result:' + finalResult)
  } catch (error) {failureCallback(error)
  }
}

4. Promise 的 API 阐明

4.1 API 阐明

Promise 构造函数

Promise (excutor) {},excutor 会在 Promise 外部立刻同步回调, 异步操作在执行器中执行

  • excutor 函数:执行器 (resolve, reject) => {}
  • resolve 函数:外部定义胜利时咱们调用的函数 value => {}
  • reject 函数:外部定义失败时咱们调用的函数 reason => {}

Promise.prototype.then 办法

(onResolved, onRejected) => {},指定用于失去胜利 value 的胜利回调和用于失去失败 reason 的失败回调返回一个新的 promise 对象

  • onResolved 函数:胜利的回调函数 (value) => {}
  • onRejected 函数:失败的回调函数 (reason) => {}

Promise.prototype.catch 办法

(onRejected) => {},onRejected 函数:失败的回调函数 (reason) => {},then() 的语法糖, 相当于:then(undefined, onRejected)

Promise.resolve 办法

(value) => {},value:胜利的数据或 promise 对象,返回一个胜利 / 失败的 promise 对象

Promise.reject 办法

(reason) => {},reason:失败的起因,返回一个失败的 promise 对象

Promise.all 办法

(promises) => {},promises:蕴含 n 个 promise 的数组,返回一个新的 promise, 只有所有的 promise 都胜利才胜利, 只有有一个失败了就间接失败

Promise.race 办法

(promises) => {},promises: 蕴含 n 个 promise 的数组,返回一个新的 promise, 第一个实现的 promise 的后果状态就是最终的后果状态

// 产生一个胜利值为 1 的 Promise 对象
const p1 = new Promise((resolve, reject) => {resolve(1)
})

// 产生一个胜利值为 2 的 Promise 对象
const p2 = Promise.resolve(2)
// 产生一个失败值为 3 的 Promise 对象
const p3 = Promise.reject(3)

p1.then(value => console.log(value))
p2.then(value => console.log(value))
p3.catch(reason => console.error(reason))

// const pAll = Promise.all([p1, p2])
const pAll = Promise.all([p1, p2, p3])
pAll.then(values => {console.log('all onResolved()', values) // all onResolved() [ 1, 2]
}, reason => {console.log('all onRejected()', reason) // all onRejected() 3})

const race = Promise.race([p1, p2, p3])
race.then(value => {console.log('all onResolved()', value) 
}, reason => {console.log('all onRejected()', reason) 
})

4.2 Promise 的几个关键问题

4.2.1 如何扭转 Promise 的状态

resolve(value),如果以后是 pendding 就会变为 resolved

reject(reason),如果以后是 pendding 就会变为 rejected

抛出异样 ,如果以后是 pendding 就会变为 rejected

const p = new Promise((resolve, reject) => {// resolve(1) // Promise 变为 resolved 胜利状态
  // reject(2) // Promise 变为 rejected 失败状态
  // Promise 变为 rejected 失败状态,reason 为抛出的 error
  throw new Error('我抛出的异样') 
  // 变为 rejected 失败状态,reason 为抛出的 3
  // throw 3
})

p.then(value => {},
  reason => {console.log('reason :', reason); }
)

4.2.2 当一个 promise 指定多个胜利 / 失败回调函数, 都会调用吗?

当 promise 扭转为对应状态时都会调用

const p = new Promise((resolve, reject) => {
  // 变为 rejected 失败状态,reason 为抛出的 3
  throw 3
})

p.then(value => {},
  reason => {console.log('reason :', reason); }
)

p.then(value => {},
  reason => {console.log('reason2 :', reason); }
)

// 后果:// reason : 3
// reason2 : 3

4.2.3 扭转 promise 状态和指定回调函数谁先谁后?

都有可能, 失常状况下是先指定回调再扭转状态, 但也能够先改状态再指定回调。

如何先改状态再指定回调?

  • 在执行器中间接调用 resolve()/reject()
  • 提早更长时间才调用 then()

什么时候能力失去数据?

  • 如果先指定的回调, 那当状态产生扭转时, 回调函数就会调用, 失去数据
  • 如果先扭转的状态, 那当指定回调时, 回调函数就会调用, 失去数据
// 惯例:先指定回调函数, 后扭转状态
new Promise((resolve, reject) => {setTimeout(() => {resolve(1) // 后扭转状态(同时指定数据),异步执行回调函数
  }, 1000);
}).then( // 先指定回调函数,保留以后指定的回调函数
  value => {},
  reason => {console.log('reason :', reason); }
)

// 先改状态, 后指定回调函数
new Promise((resolve, reject) => {resolve(1) // 先扭转状态(同时指定数据)}).then( // 后指定回调函数,异步执行回调函数
  value => {console.log('value2:', value);},
  reason => {console.log('reason2 :', reason); }
)

const p = new Promise((resolve, reject) => {resolve(1) // 先扭转状态(同时指定数据)})

setTimeout(() => {
  p.then(value => { console.log('value3:', value);},
    reason => {console.log('reason3 :', reason); }
  )
}, 1500);

4.2.4 promise.then() 返回的新 promise 的后果状态由什么决定?

简略表白:由 then() 指定的回调函数执行的后果决定

具体表白:

  • 如果抛出异样, 新 promise 变为 rejected, reason 为抛出的异样
  • 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
  • 如果返回的是另一个新 promise, 此 promise 的后果就会成为新 promise 的后果
new Promise((resolve, reject) => {resolve(1)
}).then(
  value => {console.log('onResolved1()', value); // 1
    // return 1.1 或 
    return Promise.resolve(1.1)
    // return Promise.reject(1.1)
    // throw 1.1
  },
  reason => {console.log('onRejected1()', reason);
  }
).then(value => { console.log('onResolved2()', value); }, // 1.1
  reason => {console.log('onRejected2()', reason) } // 1.1
)

4.2.5 promise 如何串连多个操作工作

promise 的 then() 返回一个新的 promise, 能够开成 then() 的链式调用,通过 then 的链式调用串连多个同步 / 异步工作。

4.2.6 promise 异样传透

当应用 promise 的 then 链式调用时, 能够在最初指定失败的回调,后面任何操作出了异样, 都会传到最初失败的回调中解决。

上面的示例代码演示了异样传透

new Promise((resolve, reject) => {// resolve(1)
  reject(1)
}).then(
  value => {console.log('onResolved1()', value);
    return 2
  }
).then(
  value => {console.log('onResolved2()', value);
    return 3
  }
).then(
  value => {console.log('onResolved3()', value);
  }
).catch(
  reason => {console.log('onRejected()', reason);  // onRejected() 1}
)

代码会执行 .catch 中的代码,但实际上代码的执行不是执行到第 3 行就间接跳转到 catch 外面了,而是从第一个 then 调用向下一个个的执行(逐级传递),然而因为咱们 then 外面没有解决异样。在 then 外面没写解决异样实际上相当于默认增加了 reason => {throw reason} 或者 reason => Promise.reject(reason)

new Promise((resolve, reject) => {reject(1)
}).then(value => {  console.log('onResolved1()', value); },
  // reason => {throw reason}
  // 或者
  reason => Promise.reject(reason)
)

Promise 的异样传透示意图

4.2.7 中断 promise 链

当应用 promise 的 then 链式调用时, 在两头中断, 不再调用前面的回调函数。

方法: 在回调函数中返回一个 pendding 状态的 promise 对象

new Promise((resolve, reject) => {resolve(1)
}).then(
  value => {console.log('onResolved1()', value);
    return new Promise(() => {}) // 返回一个 pending 的 Promise,中断 promise 链
  }
).then( // 这个 then 不会执行力
  value =>  {console.log('onResolved2()', value); }
)

5. async 与 await

Async/await 实际上只是一种基于 promises 的糖衣语法糖,Async/await 和 promises 一样,都是非梗塞式的,Async/await 让异步代码更具同步代码格调,这也是其劣势所在。

  • async function 用来定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其后果,。如果你在代码中应用了异步函数,就会发现它的语法和构造会更像是规范的同步函数。MDN async_function
  • await 操作符用于期待一个 Promise 对象。它只能在异步函数 async function 中应用。MDN await

5.1 async 函数

async 函数的返回值为 Promise 对象,async 函数返回的 Promise 的后果由函数执行的后果决定

async function fn1() {return 1}

const result = fn1()
console.log(result) // Promise {1}

在控制台能够看见如下信息

既然是 Promise 对象,那么咱们用 then 来调用,并抛出谬误,执行 onRejected() 且 reason 为错误信息为“我是谬误”

async function fn1() {
  // return 1
  // return Promise.resolve(1)
  // return Promise.reject(2)
  throw '我是谬误'

}

fn1().then(value => { console.log('onResolved()', value) },
  reason => {console.log('onRejected()', reason) } // onRejected() 我是谬误)

5.2 await 表达式

await 右侧的表达式个别为 promise 对象, 但也能够是其它的值:

  • 如果表达式是 promise 对象, await 返回的是 promise 胜利的值
  • 如果表达式是其它值, 间接将此值作为 await 的返回值
function fn2() {return new Promise((resolve, reject) => {setTimeout(() => {resolve(1000)
    }, 1000);
  })
}

function fn4() { return 6}

async function fn3() {// const value = await fn2() // await 右侧表达式为 Promise,失去的后果就是 Promise 胜利的 value
  // const value = await '还能够这样'
  const value = await fn4()
  console.log('value', value)
}

fn3() // value 6

await 必须写在 async 函数中, 但 async 函数中能够没有 await,如果 awaitPromise 失败了, 就会抛出异样, 须要通过 try...catch 捕捉解决

function fn2() {return new Promise((resolve, reject) => {setTimeout(() => {// resolve(1000)
      reject(1000)
    }, 1000);
  })
}

async function fn3() {
  try {const value = await fn2()
  } catch (error) {console.log('失去失败的后果', error)
  }
}

fn3() // 失去失败的后果 1000

5.3 Async/await 比 Promise 更优越的体现

简洁洁净 ,应用 async/await 能省去写多少行代码

错误处理 ,async/wait 能用雷同的构造和好用的经典 try/catch 解决同步和异步谬误,谬误堆栈能指出蕴含谬误的函数。

调试 ,async/await 的一个极大劣势是它更容易调试,应用 async/ await 则无需过多箭头函数,并且能像失常的同步调用一样间接跨过 await 调用。


笔记首发于我的公众号《前端全栈开发者》

退出移动版