js|异步编程方式涉及的各个知识点🎁🎁

182 阅读12分钟

一、回调函数

回调函数是一个函数做为一个参数传到另一个函数里了,在函数执行完成之后在执行。

function f1(callback) {
    setTimeout(function(){
        callback();
    },1000)
}
function f2(){
    console.log("f2执行");
}
f1(f2);

1、什么是回调函数

回调函数就是把一个函数当做一个参数传给另一个函数,做为参数的这个函数就是回调函数。

写一个向人打招呼的函数

如果向很多人打招呼可以采用map的方式

此时的greet()就是回调函数,person.map()利用一个函数做为参数,被称为高阶函数

回调函数做为高阶函数的参数,高阶函数调用回调函数来执行任务。

/**
 * 1个人打招呼
 * @param {} name 
 * @returns 
 */
function greet(name) {
    return `hello,${name}`;
}
console.log(greet('tom')); //hello,tom
/**
 * 多人打招呼
 */
const persons = ['yom','dom','com','jack']
const message = persons.map(greet);
console.log(message); //[ 'hello,yom', 'hello,dom', 'hello,com', 'hello,jack' ]

2、自己编写回调函数

/**
 * 自己编写回调函数
 * @param {*} array 
 * @param {*} callback 
 * @returns 
 */
function map(array, callback) {
    const mapArray = [];
    for (const item of array) {
        mapArray.push(
            callback(item)
        )
    }
    return mapArray;
}
function greet1(name){
    return `hello,${name}`;
}
const person = ['tom','dom'];
const message = map(person,greet1);
console.log(message); // [ 'hello,tom', 'hello,dom' ]

3、同步回调

同步回调会发生阻塞,高阶函数在回调函数执行完成后,才会继续执行

function map2(array, callback) {
    console.log("map is start");
    const mapArray = [];
    for (const item of array) {
        mapArray.push(
            callback(item)
        )
    }
    console.log("map completed");
    return mapArray;
}
function greet2(name) {
    console.log("greet in callback");
    return `hello + ${name}`;
}
const person2 = ['tom'];
const message2 = map2(person2, greet2);
console.log(message2);
// map is start
// greet in callback
// map completed
// [ 'hello + tom' ]

4、js原生的同步回调

/**
 * js原生回调replace
 */
const person4 = 'Cristina';
const replace = person4.replace(/./g, function (char) {
    return char.toLowerCase() === 'i' ? '1' : char
})
console.log(person4); //Cristina
console.log(replace); //Cr1st1na
/**
 * js原生回调reduce
 */
const person3 = ['Ana', 'Elena'];
const countStartingA = person3.reduce(
    function callback(count, name) {
        const startA = name[0].toLowerCase() === 'a';
        return startA ? count + 1 : count;
    }, 0
)
console.log(countStartingA); // 1
/**
 * js原生回调find
 */
const person3 = ['Ana', 'Elena'];
const nameStartingA = person3.find(
    function callback(name) {
        return name[0].toLowerCase() === 'a';
    }
)
console.log(nameStartingA); // Ana
/**
 * js原生回调forEach
 */
const person3 = ['Ana', 'Elena'];
person3.forEach(
    function callback(name) {
        console.log(name); // Ana  Elena
    }
)

5、异步回调

是非阻塞的,无需等待回调函数的完成,高阶函数先完成其他事件再回调函数

/**
 * 异步回调
 */
console.log('setTimeout start');
setTimeout(function later() {
    console.log("later");
}, 2000);
console.log("setTimeout completed");
// setTimeout start
// setTimeout completed
// later

js异步回调

/**
 * Dom的事件监听器
 */
const myButton = document.getElementById('myButton');
myButton.addEventListener('click', function handler() {
    console.log("Button Clicked");
})
/**
 * 异步回调计数器函数
 */
setTimeout(function later() {
    console.log('2秒之后执行');
}, 2000)
setInterval(function repeat() {
    console.log('每2秒执行一次');
}, 2000)

可以把异步函数用作异步回调

/**
 * 异步函数,做回调函数
 */
 async function fetchUserNames() {  // 异步函数
    const resp = await fetch('https://api.github.com/users?per_page=5');
    const users = await resp.json();
    const names = users.map(({ login }) => login);
    console.log(names);
  }
 const button = document.getElementById('fetchUsersButton');
 button.addEventListener('click', fetchUserNames); // 异步回调函数

思考💡setTimeout(callback,0) 执行 callback 时是同步还是异步的?(答案还有待思考)

 /**
  * 考题
  */
 function callback(){
     console.log("异步还是同步");
 }
setTimeout(callback,0); // 异步回调,无需等待callback执行,可以先执行后面的任务
console.log("我先执行");
//我先执行
// 异步还是同步

二、发布订阅模式

通过pub/sub模式,我们可以在信息中心清楚的看到有多少信号来源,方便集中管理,更加方便模块化管理,但是如果整个项目都使用pub/sub模式的话,流程就变得不太清晰,数据的得到和数据的处理分开,对于后期维护也是一个大问题。

三、Promise

promise会一直处理等待状态,直到它所包装的异步调用返回/超时/结束

promise的状态分成2类:1.解决(resolve) 2.拒绝(rejected)

var p = new Promise(function (resolve, reject) {
    setTimeout(function () {
        let result = 100;
        if (result === 50) {
            resolve(50);
        } else {
            reject(result);
        }
    }, 1000)
})
p.then(function (data) {
    console.log("成功之后返回的参数是" + data);
}).catch(function (data) {
    console.log("失败之后返回的参数是" + data);
})

1. 什么是promise

1、含义

promise是一个构造函数,只要通过new一个promise来解决异步编程

2、状态

promise是承诺的意思,首先明确promise有2个状态,从pending到resolve,或者从pending到reject,一旦状态发生改变就不会在改变,这也是承诺的含义

3、基本用法

promise接受一个函数作为参数,函数的两个参数主要是resolve和reject

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

2. promise解决了什么问题

promise解决了回调地狱的问题

1、回调地狱代码

// 回调地狱
doSomething(function (result) {
  doSomethingElse(result, function (newResult) {
    doThirdThing(newResult, function (finalResult) {
      console.log('最后结果' + finalResult);
    }, failCallBack)
  }, failCallBack)
}

2、promise代码

doSomething().then(function (result) {
  return doSomethingElse(result)
})
    .then(function (newResult) {
      return doThirdThing(newResult)
    })
    .then(function (finalResult) {
      console.log('最后结果' + finalResult);
    })
    .catch(failCallBack)

3、async代码

    async function request() {
    try {
      const result = await doSomething();
      const newResult = await doSomethingElse(result);
      const finalResult = await doThirdThing(newResult);
      console.log('最后结果' + finalResult);
    } catch (error) {
      failureCallback(error);
    }
  }

3. promise的方法

promise自己的方法,resolve、reject、all、race、any

promise原型方法,catch、then、finally

1、then

promise创建完成之后,使用then进行调用,then接受2个函数做参数,分别对于resolve的reject的去情况

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

2、catch

catch相当于then方法的第二个参数,但是catch还有一个作用就是执行resolve函数,如果抛出异常不会停止运行,会进入catch方法中

p.then((data) => {
    console.log('resolved',data);
}).catch((err) => {
    console.log('rejected',err);
});

3、all

执行机制: all 方法接受多个promise对象组成的数组做参数,所有promise的状态是reslove,all方法的状态就是reslove,有一个promise的状态是reject,all方法的状态就是reject(类似数组的every方法,全部定结果)

返回值: 成功返回的是数组,失败返回的是最先被reject的状态值

执行顺序: promise.all 返回的结果数组里面的顺序和promise.all 接收的数组参数的顺序是一致的,但是内部的执行顺序不是按照顺序的

使用场景: 有些时候我们做一个操作可能得同时需要不同的接口返回的数据,这时我们就可以使用Promise.all

let promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(22);
        resolve(1);
    }, 2000)
});
let promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(11);
        resolve(2);
    }, 1000)
});
let promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(33);
        resolve(3);
    }, 3000)
});
Promise.all([promise1, promise2, promise3]).then(res => {
    console.log(res);
})

// 1秒后输出11 2秒后输出22 3秒后输出33 [1,2,3](这个是prmise.all的返回结果) 

4、race

执行机制: race接受多个promise对象组成的数组做参数,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected。(类似数组的some方法,一个定结果)

注意点:一个promise执行完成,只是代表promise.race有了返回结果,不会影响其他promise对象的执行,只是只有先完成的Promise才会被Promise.race后面的then处理。其它的Promise还是在执行的,只不过是不会进入promise.race后面的then内。

返回值: 当最先执行的promise的值

使用场景: 有几个服务器都具有相同的接口,这个时候我们不知道哪个服务器快,就可以使用promise.race

let promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(22);
        reject(1);
    }, 2000)
});
let promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(11);
        resolve(2);
    }, 1000)
});
let promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(33);
        resolve(3);
    }, 3000)
});
Promise.race([promise1, promise2, promise3]).then(res => {
    console.log(res);
}, rej => {
    console.log(rej)
})
// 1秒后输出11 2(这个是prmise.race的返回结果) 2秒后输出22 3秒后输出33

5、finally

无论promise的状态是什么,都会执行finally方法

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

下面是一个例子,服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。

server.listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);

finally本质是then方法的封装

promise
.finally(() => {
  // 语句
});
// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。

6、any

返回机制: Promise.any接受一个可迭代的对象做为参数,(比如数组)只要其中的一个promise成功,就返回那个成功的promise,如果所有可迭代的对象中没有一个promise成功,就返回一个失败的promise。

应用场景: 从最快的服务器检索资源

//有一个成功,就返回成功
const promises = [
    Promise.reject('error a'),
    Promise.reject('error b'),
    Promise.resolve('result'),
]
Promise.any(promises).then((value) => {
    console.log("value" + value);
}).catch((err) => {
    console.log("err" + err);
})
// 浏览器状态下返回的结果
// value result
//所有的失败,就返回失败
const promises = [
    Promise.reject('error a'),
    Promise.reject('error b'),
    Promise.reject('error c'),
    Promise.reject('error d'),
]
Promise.any(promises).then((value) => {
    console.log("value" + value);
}).catch((err) => {
    console.log('err:' + err); // err: AggregateError: All promises were rejected
    console.log(err.message);// All promises were rejected
    console.log(err.name); // AggregateError
    console.log(err.errors);// (4) ["error a", "error b", "error c", "error d"]
})

4. then为什么可以链式调用

因为promise返回值还是一个promise实例,所以可以使用then链式调用

5. all、race、any对比

Promise.any VS Promise.all

Promise.all是所有的成功,才会返回成功,有一个失败,都返回失败

Promise.any是有一个成功,就会返回成功,所有的失败,才会返回失败

Promise.any VS Promise.race

Promise.any 关注Promise是否已经解决

Promise.race 关注Promise执行的状态,其中哪个最先完成就返回其状态,不管成功与否

四、genertor分析

1、genertor是什么

genertor是es6引入的新的数据类型,看着像一个函数,不过定义是由 function * xxxx(){}组成,并且可以利用yield返回多次

2、genertor的语法

function* genFn(x) {
    y = yield x + 1;
    console.log(y);
}
let g = genFn(1);
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: undefined, done: true }

第一步let g = genFn(1);调用genFn时,返回的是一个指针对象,此时并没有执行函数,只是简单的调用

第二步,采用next语法,会移动内部指针,指向第一个遇到yield的语句,就暂停执行,交出函数的执行权

第三步,再次采用next语法,会从上一次的暂停之后继续执行(就像保留了记忆一样)

next返回一个对象,包含value,done2个属性,value是yield语句后面表达式的值,done表示整个genertor函数是否执行完毕

function* genFn(x) {
    y = yield x + 1;
    return y;
}
let g = genFn(1);
console.log(g.next()); // { value: 2, done: false }
console.log(g.next(4)); // { value: 4, done: true }
console.log(g.next()); // { value: undefined, done: true }
  • next(xxx),入参会被当做上一个异步任务返回的结果,被y接受并返回

3、genertor执行机制

genertor函数调用之后并没有执行函数,只是返回一个遍历器对象,必须调用遍历器对象的next的方法,才可以使指针指向下一个状态

直到遇到下一个yield语句或者return语句才结束

3、yield表达式

yield是暂停执行的标志

遇到yield就暂停执行,并且把yield后面表达式的值,作为返回对象的value值

下一次调用next(),在继续往下执行,直到遇到下一个yield表达式

如果没有遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return后面表达式的值作为返回对象的value值

如果没有return语句,则返回对象的value值是undefined

4、yield和return的区别

区别在与每次遇到yield表达式,函数会暂停执行,下一次在从该位置之后继续执行

return语句不具备记忆功能

每个函数里只能执行一次return语句,但是可以执行多次yield表达式

yield只能用在genertor里,用到其他地方会报错

5、next()

next()本身没有返回值,或者说总是返回undefined

next可以带一个参数,这个参数会被当做上一个yield表达式的返回值

在第一次使用next()传参是无效的,V8引擎会直接忽略第一次使用next()的参数,只用第二次传参才是有效的

6、genertor处理异步

function* genFn() {
    var result = yield fetch('https://zhuanlan.zhihu.com/p/114374428');
    console.log("result",result);
}
let g = genFn();
let result = g.next();
result.value.then(data => {
    return data;
}).then(data => {
    g.next(data);
})

image (13).png

五、async/await异步处理

async 返回一个promise对象

await 后面可以接受一个promise对象

异步成功

async function h() {
    let data = await Promise.resolve(22);
    console.log(data); // 22 
    return data;
}
console.log(h());
// Promise { <pending> }
{/* 22 */}

异步失败

async function c() {
    try {
        let data = await Promise.reject(22);
        console.log(11);  // 不会执行 
    } catch (e) {
        console.log(222) // 输出 222 
    }
    return 333 
}
console.log(c());

image (12).png

1、async函数

async函数返回值为promise对象,async函数返回prmise的结果由函数的执行结果决定 既然是prmise对象,那么我们用then调用

async不会阻塞后面代码的执行,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。

async function fn1() {
  return Promise.reject(2);
}
fn1().then(
  value => { console.log('onResolved()', value) },
  reason => { console.log('onRejected()', reason) } // onRejected() 2
)

2、await表达式

(1)如果表达式是promise对象,await返回的是promise成功的值

(2)如果表达式是其他的值,将此值作为await的返回值

await 必须写在 async 函数中, 但 async 函数中可以没有 await,如果 await 的 Promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理。

await后面是普通类型

function fn2() {
  return 2;
}
async function fn3() {
  const value = await fn2(); 
  console.log(value);  // 2
}
fn3();

await后面是promise

function fn1() {
 return new Promise((resolve, reject) => {
   setTimeout(() => {
     resolve(1000)
   }, 1000);
 })
}
async function fn3(){
 const value = await fn1(); // await 右侧是promise,得到的结果就是promise成功的值
 console.log(value); // 1000 
}
fn3();
function fn2() {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
          reject(1000)
      }, 1000);
  })
}
async function fn3() {
  try {
      const value = await fn2()
  } catch (error) {
      console.log('得到失败的结果', error)
  }
}
fn3() // 得到失败的结果 1000

3、async/await 比promise更加优越

简洁干净,使用 async/await 能省去写多少行代码
错误处理,async/wait 能用相同的结构和好用的经典 try/catch 处理同步和异步错误
调试,async/await 的一个极大优势是它更容易调试,使用 async/await 则无需过多箭头函数,并且能像正常的同步调用一样直接跨过 await 调用

4、async解决什么问题

async为了解决promise的then方法的链式调用,已经最原始的回调函数产生的回调地狱的问题。

5、关于async非常有意思的面试题?

// 面试题
function getJSON() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(2);
            resolve(2)
        }, 2000)
    })
}
async function testAsync() {
    await getJSON()
    console.log(3);
}
testAsync()
//2
//3

思考💡:将async await语句解析翻译为Promise?

分析一下await做的事情: (1)将写在await后面的代码放到async内部的promise里去执行 (2)将await下面的代码放到前一个promise的then中去执行

看代码了:

function getJSON() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(2);
            resolve(2)
        }, 2000)
    })
}

function testAsync(){
  return Promise.resolve().then(()=>{
    return getJSON();
  })
  .then(()=>{
    return console.log(3)
  })
}
testAsync()
// 2
// 3

参考👀

Promise.all和Promise.race区别,和使用场景

# 消灭异步回调,还得是async-await

blog.csdn.net/weixin_3077…

segmentfault.com/a/119000003…

Generator详解

Generator

async await 的原理