JavaScript中的异步编程

异步

何为异步?

简单来讲就是一个任务分红多个步骤执行,先执行某一段任务,跳出转而执行其余任务, 等下一段任务准备完成后, 转而回来执行下一段任务前端

像这种类型, 把一个任务分解成多段任务 不连续 执行, 就叫作异步, 连续执行的则叫作同步node

如何使得异步 看起来像是同步编程 ? 有以下几种方法

回调函数

经过拆解一个任务, 分红多段,把第二段任务单独写在第二个函数内,等到须要执行这个任务时, 直接调用这个函数golang

node.js中经常使用的就是如此方法。ajax

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(){} 捕获错误
列子:promise

try{
        setTimeout(()=>{
            callback()
            throw new Error('抛出错误')
        },1000)
    }.catch(err){
        console.log('看看是否走到了这里')
    }

上面的代码是没法走到catch内部的, 因为try{}catch 只能捕获当前任务循环内的任务抛出错误, 而这个回调被存放起来, 直到下一个事件环的时候才会取出, try{}catch实在无能为力app

在node中,已约定回调的第一个参数是抛出的异常。只是用另外的方式来捕获错误。
伪代码异步

let func = function(callback){
        try{
                setTimeout(()=>{
                if(success){
                    callback(null)
                }else{
                    callback(new Error('错误'))
                }
            },1000)
        }catch(e){
            console.log('捕获错误',e);
        }
        
    }

事件监听

一般在前端操做的通常是经过addeventLisener监听各类事件,好比键盘事件 鼠标事件等等,async

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具体查看mdn文档

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();
相关文章
相关标签/搜索