JavaScript的异步解决方案

由于JavaScript是单线程的一门脚本语言(主线程是单线程)

所以异步题目是个让人常头疼的题目

我们来看一下罕见的传统处理方案

1.回调函数

回调函数是一种最罕见 最传统的体式格局 相似的这类

// node 的文件读取
let fs = require('fs');
fs.readFile('./test1.js','utf8',function(err,data){
    console.log(data) 
})

如许我们可以在回调函数里拿到文件的内容,但是如许有一个题目, 假如我要读取多个文件,每一个读取的文件都要依靠前一个读取文件的内容

比方 我test1.js的内容是test2的途径

那末就要如许写

let fs = require('fs');
fs.readFile('./test1.js','utf8',function(err,data){
    fs.readFile(data,'utf8',function(err,data){
        console.log(data)
    }) 
})

假如100个 1000个文件呢 ? 由于异步挪用没法用try{}catch 捕捉 万一中心读取失利了一次又该怎么做? 岂非每一个函数体内都if(err) 一下? 这类体式格局难以保护 也就是我们常说的回调地狱

2定阅宣布形式

我如今有如许一种需求 我须要在差别的文件里读取差别的内容, 等多个文件的内容都读取终了再一起输出

let fs = require('fs');
let result = {};
fs.readFile('./test1.js','utf8',function(err,data){
    result.test1 = data
    fs.readFile('./test2','utf8',function(err,data){
       result.test2 = data
       console.log(result)
    }) 
})

用回调体式格局会带来什么题目? 需求: 这些异步请求没有依靠关联 我须要同时提议 而不是守候上一次读取的效果

如今我们来聊聊 定阅宣布形式

定阅宣布形式定义了一种一对多的依靠关联,让多个定阅者对象同时监听某一个主题对象。这个主题对象在本身状况变化时,会关照一切定阅者对象,使它们可以自动更新本身的状况。 浅显点就事说, 我把我要操纵的事放入一个待实行的行列里, 等到达某一个前提,待实行行列顺次实行,那末上代码

let fs = require('fs');
let result = {};
class Publish {
    constructor() {
        this.list = []
    };
    on(fn){
        this.list.push(fn)
    };
    emit(string){
        alert(string)
        if (Object.keys(result).length == 2) {
            this.list.forEach(fn => {
                fn()
            })
        }
    }
}

let p = new Publish()

p.on(function () {
    console.log(result)
})

fs.readFile('./test1.js', 'utf8', function (err, data) {
    result.test1 = data
    p.emit('已读取到test1的文件')
})
fs.readFile('./test2', 'utf8', function (err, data) {
    result.test2 = data
    p.emit('已读取到test2的文件')
}) 

道理实在也就是回调函数

题目:宣布定阅跟观察者形式有什么区别??

3 Promise

幸亏我们有了Promise这个类 关于Promise的文章有许多 人人自行可以搜刮一下

我们来看下Promise A+ 范例

那依据这个范例我们简朴的写一遍promise的源码吧

我们来定义2个文件
Promise.js和require.js

//require.js
let Promise = require('./promise.js')

let p = new Promise((resolve, reject) => {
    
       setTimeout(()=>{
           resolve(100)
       },100)
   
})
p.then(function(data){
    console.log(data)
},function(e){
    console.log(e)
})
//promise.js



class Promise {
    constructor(executor){
        // promise的三个状况
        this.state = 'pending'
        this.value = undefined
        this.reason = undefined
        // 有能够挪用then的时刻 并没有resolve或许reject    所以这里用来寄存then以后要做的事
        this.onResolvedCallbacks = []   
        this.onRejectedCallbacks = []
    
        const resolve = (value) => {
        // 我们须要推断resolve出来的值是不是照样一个promise 
     
            if(value instanceof Promise){
                return value.then(resolve,reject)
            }
            // promiseA+ 范例请求这么写
            setTimeout(()=>{
                if (this.state === 'pending') {
                    this.state = 'resolved'
                    this.value = value
                    // 把保存起来的函数逐一实行然后效果传给下一个
                    this.onResolvedCallbacks.forEach( fn => {
                        return fn(value)
                    })

                }
            })
            
        }
        const reject = (reason) => {
            setTimeout(()=>{
                if (this.state === 'pending') {
                    this.state = 'rejected'
                    this.reason = reason
                    this.onRejectedCallbacks.forEach(fn => {
                        return fn(reason)
                    })
                }
            })
          
        }
        try {
            executor(resolve,reject)
        } catch (error) {
            reject(error)
        }
        
    }

    then(onFulfilled,onRejected){
         // new 的时刻立时实行executor  ----> 就是(resolve,reject)=>{   }()  拿到resolve跟reject 然后做状况推断该挪用哪一个
        onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : function (value) {
            return value
        };
        onRejected = typeof onRejected == 'function' ? onRejected : function (value) {
            throw value
        };
      

        let promise2 = new Promise((resolve,reject)=>{   
          
            if (this.state === 'resolved'){
                  //onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].
                  // 范例上要这么做  防备直接resolve 同步挪用then 这个时刻promise2不存在报错 
                  // 实行递次参考浏览器事宜环  哪天有空零丁写一篇
                setTimeout(()=>{
                
                    //  由于onFulfilled都是异步挪用 所以不能在new Promise的时刻捕捉到
                    try {
                      
                        let x = onFulfilled(this.value) // then胜利的回调 
                        resolvePromise(promise2, x, resolve, reject)  
                    } catch (error) {
                   
                        reject(error)
                    }
                })
               
            }

            if (this.state === 'rejected'){
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)  // 失利的回调
                        resolvePromise(promise2, x, resolve, reject)   
                    } catch (error) {
                        reject(error)
                    }
                  
                })
            }
            if (this.state === 'pending'){  //  假如executor是个异步要领  那末会先挪用then 所以这里把胜利回调跟失利的回调都存起来
           
                this.onResolvedCallbacks.push((value)=>{
                
                    try {
                      
                        let x = onFulfilled(value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                     
                        console.log(error)
                        reject(error)
                    }
                  
                })
                
                this.onRejectedCallbacks.push((reason)=>{
                    try {
                        let x = onRejected(reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                  
                })
            }
        })   
        //  then返回一个promise 
        return promise2 
    }
    catch(onRejected){
        return this.then(null, onRejected);
    }
    static all(promises){
        return new Promise(function (resolve, reject) {
            let result = [];
            let count = 0;
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(function (data) {
                    result[i] = data;
                    if (++count == promises.length) {
                        resolve(result);
                    }
                }, function (err) {
                    reject(err);
                });
            }
        });
    }
}

const resolvePromise = (promise2, x, resolve, reject)=>{
    // promise2 跟 then的胜利回调返回值有多是同一个值
    if(promise2 === x){
        return reject(new TypeError('报错 轮回引用了'))
    }
    let then,called;
    // 要么对象要么函数
    if(x !== null&&((typeof x === 'object' || typeof x === 'function')) ){
        try {
            then = x.then   // 有多是getter定义的会报错
            // then 有多是个函数或许一般值
            if(typeof then === 'function'){
                // 假如then是个函数的话  就以为它是个promise
                then.call(x,function(){
                    if(called) return
                    called = true  
                    resolvePromise(promise2, y, resolve, reject);
                },function(error){
                    if (called) return
                    called = true  
                    reject(error)
                })
            }else{
                resolve(x)
            }
        } catch (error) {
            if (called) return
            called = true  
            reject(error)
        }
      
    }else{
        // x是个一般值
        resolve(x)
    }
}


module.exports = Promise

实行require.js的效果是
《JavaScript的异步解决方案》

如许我们就完成了一个promise 是不是是很棒棒? 如今我们可以promise.then 链式挪用了 然后用catch做一致毛病处理 处理了上面毛病捕捉的题目 另有没有更好的要领? 当然有!

4 生成器 迭代器

这篇文章讲的比较细致:迭代器

在讲async await 之前 我们先讲一下 生成器

**当你在实行一个函数的时刻,你可以在某个点停息函数的实行,而且做一些其他事情,然后再返回这个函数继承实行, 以至是照顾一些新的值,然后继承实行。
上面形貌的场景恰是JavaScript生成器函数所致力于处理的题目。当我们挪用一个生成器函数的时刻,它并不会马上实行, 而是须要我们手动的去实行迭代操纵(next要领)。也就是说,你挪用生成器函数,它会返回给你一个迭代器。迭代器会遍历每一个中断点。
next 要领返回值的 value 属性,是 Generator 函数向外输出数据;next 要领还可以接收参数

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 }

Generator函数的标志就是function关键词后联缀一个’*’ 合营yield 停息函数 返回的是一个迭代器 每次实行next的时刻 停在yield

我们都见过类数组构造吧

let likeArray = { 0: 1, 1: 2, 2: 3, length: 3 }
let arr = [...likeArray]
//实行这段代码会报错  报错信息likeArray is not iterable    likeArray是不可枚举的 那末我们假如想完成如许的类数组转为数组 怎么办呢 

我们先看一下函数里的argument跟类数组有什么区别

function(){
console.log(argument)}

《JavaScript的异步解决方案》

我们改下一下类数组构造

let likeArray = { 0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator](){
        return {
            next() {
                return {
                    value: 1,
                    done: false
                }
            }
        }
    } 
}
//在实行
let arr = [...likeArray]
控制台报错FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

觉得是不是是有点像那末回事了 
我们再改写
let likeArray = { 0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator](){

       let index = 0 
       let self  = this
        return {
            next() {
                return {
                    done: self.length === index
                    value: self[index++],
                    
                }
            }
        }
    } 
}

// 输出[1,2,3]    只需在done是false的时刻示意迭代完成 就不再继承实行了 value是每次迭代返回的值
再改写一下
let likeArray = {
    0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator]:function*() {

        let index = 0;
        while (index !== this.length) {
            yield this[index++]
        }

    }
}


console.log([...likeArray])  //[1,2,3]  挪用返回一个迭代器   ... 每次挪用迭代器的next要领 返回{value,done}

生成器可以合营node.js中的co, 借助于Promise,你可以运用越发文雅的体式格局编写非壅塞代码。

例子:

let fs = require('fs');
function readFile(filename) {
  return new Promise(function (resolve, reject) {
    fs.readFile(filename, function (err, data) {
      if (err)
        reject(err);
      else
        resolve(data);
    })
  })
}
function *read() {
  let template = yield readFile('./template.txt');
  let data = yield readFile('./data.txt');
  return template + '+' + data;
}
co(read).then(function (data) {
  console.log(data);
}, function (err) {
  console.log(err);
});

5 async/await

有了上面的基本 async/await 越发轻易邃晓了
async/await的长处有
1.内置实行器
2.更好的语义
3.更广的适用性

let fs = require('fs');
function readFile(filename) {
  return new Promise(function (resolve, reject) {
    fs.readFile(filename, 'utf8', function (err, data) {
      if (err)
        reject(err);
      else
        resolve(data);
    })
  })
}

async function read() {
  let template = await readFile('./template.txt');
  let data = await readFile('./data.txt');
  return template + '+' + data;
}
let result = read();
result.then(data=>console.log(data));

可以直接await 一个promise 使得异步代码实行看起来像同步一样 更文雅

async 函数的完成,就是将 Generator 函数和自动实行器,包装在一个函数里。

async function read() {
  let template = await readFile('./template.txt');
  let data = await readFile('./data.txt');
  return template + '+' + data;
}

// 等同于
function read(){
  return co(function*() {
    let template = yield readFile('./template.txt');
    let data = yield readFile('./data.txt');
    return template + '+' + data;
  });
}

**总结: 异步处理方案另有其他的一些要领 不过都不主要 我们只需控制了async/await 用async/await写异步代码 更轻易保护
第一次写文章 写的不好多多见谅 毕竟许多东西都是站在前任人的肩膀上直接拿过来的**

    原文作者:槐破梦
    原文地址: https://segmentfault.com/a/1190000017196873
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞