异步
何为异步?
简单来说就是一个任务分成多个步骤执行,先执行某一段任务,跳出转而执行其他任务, 等下一段任务准备完成后, 转而回来执行下一段任务
像这种类型, 把一个任务分解成多段任务 不连续 执行, 就叫做异步, 连续执行的则叫做同步
如何使得异步 看起来像是同步编程 ? 有如下几种方法
回调函数
通过拆解一个任务, 分成多段,把第二段任务单独写在第二个函数内,等到需要执行这个任务时, 直接调用这个函数
node.js中常用的就是如此方法。
fs.readFile('某个文件', function (err, data) {
if (err) throw err;
console.log(data);
});
这是一个错误优先的回调函数(error-first callbacks),这也是Node.js本身的特点之一。 类似golang中的err 错误处理
带来的问题:
- 多层嵌套问题
回调带来一些问题, 第一个就是多层嵌套问题, 当一个问题很复杂, 多段不连续, 就会出现地狱嵌套问题
fs.readFile('某个文件', function (err, data) {
if (err) throw err;
fs.writeFile('某个文件',data, function (err, data) {
if (err) throw err;
fs.readFile('某个文件', function (err, data) {
if (err) throw err;
console.log("写入的是:",data)
});
});
});
异常处理
无法使用try{}catch(){} 捕获错误
列子:
try{
setTimeout(()=>{
callback()
throw new Error('抛出错误')
},1000)
}.catch(err){
console.log('看看是否走到了这里')
}
上面的代码是无法走到catch内部的, 由于try{}catch 只能捕获当前任务循环内的任务抛出错误, 而这个回调被存放起来, 直到下一个事件环的时候才会取出, try{}catch实在无能为力
在node中,已约定回调的第一个参数是抛出的异常。只是用另外的方式来捕获错误。
伪代码
let func = function(callback){
try{
setTimeout(()=>{
if(success){
callback(null)
}else{
callback(new Error('错误'))
}
},1000)
}catch(e){
console.log('捕获错误',e);
}
}
事件监听
通常在前端操作的一般是通过addeventLisener监听各种事件,比如键盘事件 鼠标事件等等,
document.addeventListener('click',function(e){
console.log(e.target)
},false)
事件发布订阅
通常把需要执行的任务先暂存起来, 等达到条件或者发布的时候一一拿出来执行
- 伪代码
class Task{
construct(){
this.tasks = {}
}
publish(event){
this.tasks[event].forEach(fn=>fn())
}
subscribe(event,eventTask){
this.tasks[event] = this.tasks[event] ? this.tasks[event] : []
this.tasks[event].push(eventTask)
}
}
let task = new Task()
task.subscribe('eat',function(){console.log('吃午饭')})
task.subscribe('eat',function(){console.log('吃晚饭')})
task.publish('eat')
-
Promise/Deferred模式
-
生成器Generators/ yield
- 当你在执行一个函数的时候,你可以在某个点暂停函数的执行,并且做一些其他工作,然后再返回这个函数继续执行, 甚至是携带一些新的值,然后继续执行。
- 上面描述的场景正是JavaScript生成器函数所致力于解决的问题。当我们调用一个生成器函数的时候,它并不会立即执行, 而是需要我们手动的去执行迭代操作(next方法)。也就是说,你调用生成器函数,它会返回给你一个迭代器。迭代器会遍历每个中断点。
- next 方法返回值的 value 属性,是 Generator 函数向外输出数据;next 方法还可以接受参数,这是向 Generator 函数体内输入数据
-
function* foo () {
var index = 0;
while (index < 2) {
yield index++; //暂停函数执行,并执行yield后的操作
}
}
var bar = foo(); // 返回的其实是一个迭代器
console.log(bar.next()); // { value: 0, done: false }
console.log(bar.next()); // { value: 1, done: false }
console.log(bar.next()); // { value: undefined, done: true }
yield是一个表达式, 后面紧跟着的表达式是next()的返回结果的value,而如果想给yield传递参数,比如a=yield 1,给a传递值 则要next(value))
// 例子:
function* foo () {
a = yield 1
console.log(a) // 10
}
var bar = foo(); // 返回的其实是一个迭代器
console.log(bar.next()); // { value: 1, done: false }
console.log(bar.next(10)); // { value: undefined, done: true }
可以理解为yield有两步操作,第一个next弹出值,第二个next接收值并且执行一下段语句,直到下一个yield弹出值为止
利用yield转换多维数组
function* iterArr(arr) { //迭代器返回一个迭代器对象
if (Array.isArray(arr)) { // 内节点
for(let i=0; i < arr.length; i++) {
yield* iterArr(arr[i]); // (*)递归
}
} else { // 离开
yield arr;
}
}
var arr = [ 'a', ['b',[ 'c', ['d', 'e']]]];
var gen = iterArr(arr);
arr = [...gen];
利用yield解决异步问题
function* main(){
try{
let result = yield foo()
console.log(result)
}catch(e){
console.log(e)
}
}
let it = main()
function foo(params,url){
$.ajax('www.baidu.com',
function(err,data){
if(err){
it.throw(err)
}else{
it.next(data)
}
}
}
it.next() // {value:undefind,done:false}
promise
Promise 对象用于表示一个异步操作的最终完成 (或失败), 及其结果值.
他有三个状态 分别是pending resolved 以及rejected
一旦发生状态改变, 就不可再更改了 每次then都另外创建一个promise对象
// 伪代码
class Promise{
construct(executor){
let self = this
// 一个 Promise有以下几种状态:
// pending: 初始状态,既不是成功,也不是失败状态。
// fulfilled: 意味着操作成功完成。
// rejected: 意味着操作失败。
this.status = 'pending'
this.res = undefined // 存成功之后的值
this.err = undefined // 存失败之后的值
this.onFulfilledCallback = []
this.onRejectedCallback = []
function resolve(res){
if(self.status === 'pending'){
self.status = 'resolved'
self.res = res
onFulfilledCallback.forEach(fn=>fn())
}
}
function reject(err){
if(self.status === 'pending'){
self.status = 'rejected'
self.err = err
onRejectedCallback.forEach(fn=>fn())
}
}
// executor是带有 resolve 和 reject 两个参数的函数 。Promise构造函数执行时立即调用executor 函数, resolve 和 reject 两个函数作为参数传递给executor(executor 函数在Promise构造函数返回所建promise实例对象前被调用)。resolve 和 reject 函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。executor 内部通常会执行一些异步操作,一旦异步操作执行完毕(可能成功/失败),要么调用resolve函数来将promise状态改成fulfilled,要么调用reject 函数将promise的状态改为rejected。如果在executor函数中抛出一个错误,那么该promise 状态为rejected。executor函数的返回值被忽略。
executor(resolve,reject)
}
then(onFulfilled,onRejected){
let self = this
return new Promise((resolve,reject)=>{
if(self.status === 'resolved'){
let x = onFulfilled(self.res) // 拿到onFulfilled的执行结果 注意:这里执行的是Promise.resolve() 同步代码
// 然后把x传递给下一个then
resolve(x)
}
if(self.status === 'rejected'){
let x = onRejected(self.res) // 拿到onFulfilled的执行结果 注意:这里执行的是Promise.resolve() 同步代码
// 然后把x传递给下一个then
reject(x)
}
if(self.status === 'pending'){
self.onFulfilledCallback.push(function(){
let x = onFulfilled(self.res) // 这里的self.res 是上一个new Promise上的值 此时的onFUlfilled 相当于 fn(){let x = onFulfilled}
resolve(x)
})
self.onRejectedCallback.push(function(){
let x = onRejected(self.res) // 这里的self.res 是上一个new Promise上的值 此时的onFUlfilled 相当于 fn(){let x = onFulfilled}
reject(x)
})
}
})
}
}
使用promise
function request(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve({data:'获得数据'})
},1000)
})
}
request().then(data=>{
console.log(data)
})
链接:Promise
yield 配合Promise
function foo(x,y) {
return request(
"http://some.url.1/?x=" + x + "&y=" + y
);
}
function *main() {
try {
var text = yield foo( 11, 31 );
console.log( text );
}
catch (err) {
console.error( err );
}
}
let it = main()
it.next()
var text = yield foo( 11, 31 )跟async await 是不是很像?
编写一个生成器
生成器可以 yield 一个 promise,然后这个 promise 可以被绑定,用其完成值来恢复这个生成器的运行。
// 伪代码
function run(gen){ // 参数是一个gen函数
let it = gen.apply(this)
return Promise.resolve().then((value)=>{
let next = it.next(value)
return (function nextHandle(next){
if(next.done === true){
return next.value
}else{
return Promise.resolve(next.value).then(nextHandle) // 递归
}
})(next)
})
}
function* main(){
function *main() {
try {
var text = yield foo( 11, 31 );
console.log( text );
}
catch (err) {
console.error( err );
}
}
}
run(main).then((data)=>{
// do something
})
AsyncFunction
AsyncFunction 构造函数用来创建新的 异步函数 对象,JavaScript 中每个异步函数都是 AsyncFunction 的对象。
注意,AsyncFunction 并不是一个全局对象,需要通过下面的方法来获取
Object.getPrototypeOf(async function(){}).constructor
语法:new AsyncFunction([arg1[, arg2[, ...argN]],] functionBody)
var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
var a = new AsyncFunction('a',
'b',
'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);');
a(10, 20).then(v => {
console.log(v); // 4 秒后打印 30
});
但是上面这种方式不高效 因为通过字面量创建的异步函数是与其他代码一起被解释器解析的,而new这种方式的函数体是单独解析的。
通过字面量创建
async a(a,b){
return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b)
}
await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行 async function。
若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。
另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。