
虽然我对js的鄙视一直都是无以复加,但是奈何前端环境不得不依赖javascript。哪些nodejs的大神们四处布道nodejs统治一切:单线程非阻塞,高IO操作。但是,java也可以做好吧,而且GO做的更干练!假设你的应用程序要做两件事情,分别是A和B。你发起请求A,等待响应,出错。发起请求B,等待响应,出错。Go语言的阻塞模型可以非常容易地处理这些异常,而换到了Node里,要处理异常就要跳到另一个函数里去,事情就会变得复杂。
Node的非阻塞模型没有了多线程,但却多出了“回调地狱”问题。
所以在此谈下JS的异步回调:promise yield async/await
对本篇的基础知识,安利下:
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
promise基本特性:

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
promise 案例:
function timeout(value) {
return new Promise(((resolve) => {
setTimeout(() => {
value++;
resolve(value);
}, 500);
}));
}
timeout(0).then((res) => {
console.log('res');
console.log(res);
});
async function test(value) {
console.log('value');
const c = await timeout(value);
console.log(c);
}
test(0);Promise是ES6之后原生的对象,我们只需要实例化Promise对象就可以直接使用。
下面来一道提:
function f () {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(1);
resolve(2);
console.log('aaa')
}, 0);
});
}
let p = f();
p.then((res) => {
console.log('resolve');
console.log(res);
},(res) => {
console.log('reject')
console.log(res);
}).catch(res => {
console.log('catch')
console.log(res);
});注意:一个promise,只有第一个reject操作失败结果,非Promise链中reject不会影响后面.then()的执行,并且如果reject和catch两种方式同时使用的话,已经reject处理了,catch不再捕获。
Promise.all(),怎么按顺序执行?
Promise.all()是并行的,等最慢的执行完后完成,在按照发起请求的先后,结果合并到数组里。
Promise.all 里的任务列表[asyncTask(1),asyncTask(2),asyncTask(3)],是按照顺序发起的。 它们是异步的,互相之间并不阻塞,每个任务完成时机是不确定的,尽管如此, 所有任务结束之后,它们的结果仍然是按顺序地映射到resultList里,这样就能和Promise.all里的任务列表[asyncTask(1),asyncTask(2),asyncTask(3)]一一对应起来。
demo
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1000);
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(100);
}, 100);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(500);
}, 500);
});
Promise.all([p1, p3, p2]).then((results) => {
console.log(results);
});
Promise.race([p1, p3, p2]).then((results) => {
console.log(results);
});如果这个promise队列里出现了reject,那么Promise.all()返回的结果会被一个reject而报销(其他正常返回也没用了)
比如第一个 p1,是reject,就会报错。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(1000);
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(100);
}, 100);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(500);
}, 500);
});
Promise.all([p1, p3, p2]).then((results) => {
console.log(results);
});所以,第一个需要catch
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(1000);
}, 1000);
}).catch((res) => {
console.log(res);
});对我们日常工作中的使用,比如多个请求合并。
import axios from 'axios';
const requests = ['requestData1', 'requestData1'];
const promises = requests.map(req => axios.get('url', req).catch((resp) => {
// TODO
}));
Promise.all(promises).then((results) => {
results.forEach((res) => {
// TODO
});
});race根据传入的多个Promise实例,只要有一个实例resolve或者reject,就只返回该结果,其他实例不再执行。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p1');
resolve(1000);
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p2');
resolve(100);
}, 100);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p3');
resolve(500);
}, 500);
});
Promise.race([p1, p3, p2]).then((results) => {
console.log(results);
});只会执行最先执行的 p2 resolve
function promiseA (callback) {
this.status = 'pending'
let doneList = []
let failList = []
this.then = function (done, fail) {
switch (this.status) {
case 'pending':
doneList.push(done)
failList.push(fail)
break
case 'fulfilled':
done()
return this
case 'rejected':
fail && fail()
return this
}
}
function resolve (result) {
this.status = 'fulfilled'
setTimeout(function () {
let value = result
for (let i = 0; i < doneList.length; i++) {
let temp = doneList[i](value)
if (temp instanceof PromiseA) {
for (i++; i < doneList.length; i++) {
temp.then(doneList[i], failList[i])
}
} else {
value = temp
}
}
}, 0)
}
function reject (error) {
this.status = 'rejected'
setTimeout(function () {
let value = error
let temp = failList[0] && failList[0](value)
for (let i = 1; i < failList.length; i++) {
if (temp instanceof PromiseA) {
temp.then(doneList[i], failList[i])
} else {
doneList.shift()
failList.shift()
value = temp
resolve(value)
}
}
}, 0)
}
callback(resolve,reject)
}promise它只是减少了嵌套,并不能完全消除嵌套;另外,采用Promise的代码看起来依然是异步的。
推荐阅读《Promise简单实现(正常思路版)》《Promises/A+规范》,这里不再赘述。
Promise特别需要注意的是,他的异常处理。
.catch()的作用是捕获Promise的错误,与then()的rejected回调作用几乎一致。但是由于Promise的抛错具有冒泡性质,能够不断传递,这样就能够在下一个catch()中统一处理这些错误。同时catch()也能够捕获then()中抛出的错误,所以建议不要使用then()的rejected回调,而是统一使用catch()来处理错误。推荐阅读:《看这一篇就够了!浅谈ES6的Promise对象》
其实这部分可以忽略,跳到Async/await部分了
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
Generator 函数是一个普通函数,但是有两个特征。
function* helloWorldGenerator() {
yield 'hello';//该函数的状态
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
console.log(hw.next()); //{ value: 'hello', done: false }
//Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值hello,done属性的值false,表示遍历还没有结束。
console.log(hw.next()); //{ value: 'world', done: false }
//第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值world,done属性的值false,表示遍历还没有结束。
console.log(hw.next()); //{ value: 'ending', done: true }
console.log(hw.next()); //{ value: undefined, done: true }
//第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。
for(let v of helloWorldGenerator()){
console.log(v); // hello world
}yield概念,这里提出来看下:
由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。
遍历器对象的next方法的运行逻辑如下
需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
yield表达式与return语句既有相似之处,也有区别。
相似之处:都能返回紧跟在语句后面的那个表达式的值。
区别在于:每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式。
我是不太喜欢yield这个模式的,自认为是一个狗血模式!
async / await是ES7的重要特性之一,也是目前社区里公认的优秀异步解决方案,Async/await建立于Promise之上——async函数的返回值为promise对象。
推荐阅读《Javascript中的async await》
首先推荐读下《ES6 Async/Await 完爆Promise的6个原因》,虽然这个标题就狗血,Promise也并非什么终极解决方案,还有待改善。但是其中的内容页没有必要重写。节选一部分如下:
Async/await使得处理同步+异步错误成为了现实。我们同样使用try/catch结构,但是在promises的情况下,try/catch难以处理在JSON.parse过程中的问题,原因是这个错误发生在Promise内部。想要处理这种情况下的错误,我们只能再嵌套一层try/catch,就像这样:
const makeRequest = () => {
try {
getJSON()
.then(result => {
// this parse may fail
const data = JSON.parse(result)
console.log(data)
})
// uncomment this block to handle asynchronous errors
// .catch((err) => {
// console.log(err)
// })
}
catch (err) {
console.log(err)
}
}但是,如果用async/await处理,一切变得简单,解析中的错误也能轻而易举的解决:
const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
}
catch (err) {
console.log(err)
}
}Async/await项目项目应用
fetch
async function getData () {
//await the response of the fetch call
let response = await fetch('https://api.github.com/users');
//proceed once the first promise is resolved.
let data = await response.json();
//proceed only when the second promise is resolved
return data;
}
//call getData function
getData().then(data => console.log(data));//log the dataaxios
class App extends React.Component{
constructor(){
super();
this.state = {
serverResponse: ''
}
}
componentDidMount(){
this.getData();
}
async getData(){
const res = await axios.get('url-to-get-the-data');
const { data } = await res;
this.setState({serverResponse: data})
const res = await axios.post('url-to-post-the-data', {username,password});
}
render(){
return(
{this.state.serverResponse}
);
}}对于异步函数的嗑叨
其中settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行;
promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;
async函数表示函数里面可能会有异步方法,
await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。
参考文章:
对Promise.all执行顺序的深入理解 https://zhuanlan.zhihu.com/p/93889764
Promise 源码和分析 https://segmentfault.com/a/1190000023586499
转载本站文章《Javascript异步回调细数:promise yield async/await》, 请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/js6/2017_0118_7944.html
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。