async, await, promise 面试题
promise function 的一般格式
// 要return 一个 promisefunction fetch(a) {return new Promise((resolve, reject) => {setTimeout(() => {resolve(`got ${a}`)}, 2000);})}
callback 变为 promise
return new Promise(function(resolve, reject){// do sthsome_cb_function(arg, function(err, msg){if(err){return reject(err)}resolve(msg)})})
.then 的写法
fetch("a").then(function (data) {console.log(data)})fetch("a").then(function (data) {console.log(`return from fetch a ----${data}`)return data}).then(function (b) {console.log(`return from fetch a then ----${b}`)console.log(`began fetch b then`)return fetch("b").then(function (res) {console.log(`return from fetch b----${res}`)return res})}).then(function (c) {console.log(`return from fetch b then ----${c}`)console.log(`began fetch c`)fetch("c").then(function (res) {console.log(`return from fetch c----${res}`)console.log(res)})})
async await 的写法
async function fun_b() {let fetch_a = await fetch("a")console.log(fetch_a)}fun_b()
callback hell
function loadImg(src, callback, fail){var img = document.createElement("img")img.onload = function () {callback(img)}img.onerror = function (err) {fail(err)}img.src = src}var src = 'https://www.imooc.com/static/img/index/logo.png'loadImg(src, function(img){console.log(img.width)},function(err){console.log(err)})
change to promise async await
function loadImg(src){const promise = new Promise(function(resolve, reject){var img = document.createElement('img')img.onload = function(){resolve(img)}img.onerror = function(err){reject(err)}img.src = src})return promise}var src = 'https://www.imooc.com/static/img/index/logo.png'// then 的写法loadImg(src).then(function(res){console.log(res.width)}).catch(function(err){console.log(err)})// async awit 写法async function toLoadImg(src){try {let res = await loadImg(src)console.log(res.width)} catch (error) {console.log(error)}}toLoadImg(src)
串联
var src1 = 'https://www.imooc.com/static/img/index/logo.png'var src2 = 'https://img.mukewang.com/user/5a9fc8070001a82402060220-100-100.jpg'var result1 = loadImg(src1)var result2 = loadImg(src2)result1.then(function (img1) {console.log(`first img finished: ${img1.width}`)return result2}).then(function (img2) {console.log(`second img finished: ${img2.width}`)}).catch(function (err) {console.log(err)})
说明: result1 和 result2 的请求 会在声明result1和result2时就发送出去,then 可以控制请求结果的返回顺序,以便使前面异步请求的结果可以给后面异步请求用
Promise.all
//Promise.all 接受多个 promise 对象 组成的数组// 待全部完成之后,统一执行 successPromise.all([result1, result2]).then(datas =>{// 接收到的 datas 是一个数组,依次包含了多个 promise 返回的内容console.log(datas[0])console.log(datas[1])})
Promise.race
//Promise.race 接受多个 promise 对象 组成的数组// 只要有一个完成,就执行 successPromise.race([result1, result2]).then(data =>{// data 是最先执行完成的 promise 返回的内容console.log(data)})
Promise的原理
在Promise的内部,有一个状态管理器的存在,有三种状态:pending、fulfilled、rejected(1) promise 对象初始化状态为 pending。(2) 当调用resolve(成功),会由pending => fulfilled。(3) 当调用reject(失败),会由pending => rejected。Promise状态一旦改变,无法再发生变更
promise.then
// onFulfilled 是用来接收promise成功的值// onRejected 是用来接收promise失败的原因promise.then(onFulfilled, onRejected);If onFulfilled is not a function, it must be ignored.If onRejected is not a function, it must be ignored.Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)// 返回1
catch
catch 在链式写法中可以捕获前面then中发送的异常。其实,catch相当于then(null,onRejected),前者只是后者的语法糖而已
实现一个简单的Promise
function Promise(fn){var status = 'pending'function successNotify(){status = 'fulfilled'//状态变为fulfilledtoDoThen.apply(undefined, arguments)//执行回调}function failNotify(){status = 'rejected'//状态变为rejectedtoDoThen.apply(undefined, arguments)//执行回调}function toDoThen(){setTimeout(()=>{ // 保证回调是异步执行的if(status === 'fulfilled'){for(let i =0; i< successArray.length;i ++) {successArray[i].apply(undefined, arguments)//执行then里面的回掉函数}}else if(status === 'rejected'){for(let i =0; i< failArray.length;i ++) {failArray[i].apply(undefined, arguments)//执行then里面的回掉函数}}})}var successArray = []var failArray = []fn.call(undefined, successNotify, failNotify)return {then: function(successFn, failFn){successArray.push(successFn)failArray.push(failFn)return undefined // 此处应该返回一个Promise}}}
for 循环 在async 函数里面,如果for 循环里面有异步请求,则异步请求按照循序依次 执行当前---完成当前---执行下一个
let mockApi = function(param){return new Promise((resolve, reject)=>{setTimeout(()=>resolve(param),2000)})};let arr_length = 10;let arr = [...new Array(arr_length).keys()];(async function(){for (let index = 0; index < arr.length; index++) {console.log(`for begin ${index}`)let result = await mockApi(index)console.log(result)}})()// outcomefor begin 00for begin 11...for begin 99
promise 基础
Promise同步执行,promise.then异步执行
const P1 = new Promise((res, rej) => {console.log('first');res();console.log('second')});P1.then(() => {console.log('third')})console.log('fourth');//output:firstsecondfourththird
只能执行一次resolve或reject, 剩下的不会执行,即使reject后有一个resolve调用
const p1 = new Promise((res, rej) => {res('1');rej('error');res('2')});p1.then(res => {console.log('then: ', res)}).catch(err => {console.log('catch: ', err)})//then: 1
promise 的 .then或.catch可以被多次调用,但是此处Promise构造函数仅执行一次。换句话说,一旦promise的内部状态发生变化并获得了一个值,则随后对.then或.catch的每次调用都将直接获取该值。
const p1 = new Promise((res, rej) => {setTimeout(() => {console.log('first')res('second')},1000)});const start = Date.now()p1.then(res => {console.log(res, Date.now() - start, "third")})p1.then(res => {console.log(res, Date.now() - start, "forth")})//output:firstsecond 1010 thirdsecond 1011 forth
.then或.catch返回的值不能是promise本身,否则将导致无限循环。
const promise = Promise.resolve().then(() => {return promise})promise.catch(err =>{console.log(err)})//TypeError: Chaining cycle detected for promise #<Promise>
It is possible to change this behaviour by handling possible rejections
有时候我们使用Promise.all()执行很多个网络请求,可能有一个请求出错,但我们并不希望其他的网络请求也返回reject,要错都错,这样显然是不合理的
我们需要的是即使有一两个接口请求失败,我们依然可以获取到成功的接口请求数据
var p1 = new Promise((resolve, reject) => {setTimeout(() => resolve('p1_delayed_resolution'), 1000);});var p2 = new Promise((resolve, reject) => {reject(new Error('p2_immediate_rejection'));});Promise.all([p1.catch(error => { return error }),p2.catch(error => { return error }),]).then(values => {console.log(values[0]) // "p1_delayed_resolution"console.log(values[1]) // "Error: p2_immediate_rejection"})
var promiseArray = []promiseArray.push(promiseResove(1))promiseArray.push(promiseReject(3))promiseArray.push(promiseResove(2))// 将传入promise.all的数组进行遍历,如果catch住reject结果,// 直接返回,这样就可以在最后结果中将所有结果都获取到var handlePromise = Promise.all(promiseArray.map(function(promiseItem) {return promiseItem.catch(function(err) {return err})}))handlePromise.then(function(values) {console.log('all promise are resolved', values)}).catch(function(reason) {console.log('promise reject failed reason', reason)})
并发处理多个 异步
第一步:拿到所有的用户信息; 第二步:针对每一个用户,要拿到用户的ID 第三步:针对每一个用户ID,将ID 变为大写
分析:
这三步都是异步操作,第一步要首先完成,然后针对所有用户的第二步和第三步,
我们可以用Promise.all并发完成, 每一个user 的第三步要依赖第二步的值,
所以第二步和第三步放在一个async 函数里面,await 完第二个函数,在await 第三个函数
// First promise returns an arrayconst getUsers = () => {return new Promise((resolve, reject) => {return setTimeout(() => resolve([{ id: 'jon' }, { id: 'andrey' }, { id: 'tania' }]), 2000)})}// Second promise relies on the resulting array of first promiseconst getIdFromUser = users => {return new Promise((resolve, reject) => {return setTimeout(() => resolve(users.id), 1000)})}// Third promise relies on the result of the second promiseconst capitalizeIds = id => {return new Promise((resolve, reject) => {return setTimeout(() => resolve(id.toUpperCase()), 1000)})}const runAsyncFunctions = async () => {const users = await getUsers()Promise.all(users.map(async user => {const userId = await getIdFromUser(user)console.log(userId)const capitalizedId = await capitalizeIds(userId)console.log(capitalizedId)return capitalizedId})).then(values =>{console.log(`values ===== ${JSON.stringify(values)}`)})console.log(`users == ${JSON.stringify(users)}`)}runAsyncFunctions()
利用 p-limit 控制Promise.all并发请求数量
const pLimit = require('p-limit');//es6 synctax//import pLimit from "p-limit";const limit = pLimit(2);// Only 2 promise is run at onceconst input = [limit(() => fetchSomething('foo')),limit(() => fetchSomething('bar')),limit(() => doSomething())];(async () => {const result = await Promise.all(input);console.log(result);})();
手写promise
const STATE={PENDING: "PENDING",FULFILLED: "FULFILLED",REJECTED: "REJECTED"}function isThenable(val){return val instanceof MyPromise}class MyPromise{constructor(callback){this.state = STATE.PENDING;this.value = undefined;this.handlers = [];try {callback(this._resolve.bind(this), this._reject.bind(this))}catch(err){this._reject(err)}}_resolve(value){this.updateResult(value, STATE.FULFILLED)}_reject(err){this.updateResult(err, STATE.REJECTED)}updateResult(value, state){setTimeout(() => {if(this.state !== STATE.PENDING){return}// check is value is also a promiseif (isThenable(value)) {return value.then(this._resolve, this._reject);}this.value = value;this.state = state;this.executeHandlers()},0)}executeHandlers(){if(this.state === STATE.PENDING){return}this.handlers.forEach((handler) => {if(this.state === STATE.FULFILLED){return handler.onSuccess(this.value)}return handler.onFail(this.value)})// After processing all handlers, we reset it to empty.this.handlers = [];}addHandlers(handler){this.handlers.push(handler)this.executeHandlers();}then(onSuccess, onFail){return new Promise((res, rej) => {this.addHandlers({onSuccess: function(val){if(!onSuccess){return res(val)}try{return res(onSuccess(val))} catch(err){return rej(val)}},onFail: function(err){if(!onFail){return rej(err)}try{return rej(onFail(err))} catch(err){return rej(err)}}})})}catch(onFail){this.then(null, onFail)}finally(callback){return new MyPromise((res, rej) => {let val;let wasRejected;this.then((value) => {wasRejected = false;val = value;return callback();}, (err) => {wasRejected = true;val = err;return callback();}).then(() => {// If the callback didn't have any error we resolve/reject the promise based on promise stateif(!wasRejected) {return res(val);}return rej(val);})})}}function fetchData(success) {return new Promise((resolve, reject) => {console.log(`promise callback initialized`)setTimeout(() => {if (success) {resolve("willem");} else {reject('my error info o');}}, 5000);});}fetchData(true).then((value1) => {console.log(value1, `then`)return `value from first then`}).then((value2) => {console.log(value2, `then2`)return `value from second then`}).finally( (val)=> {console.log(`final val === ${val}`)})