资讯专栏INFORMATION COLUMN

js异步发展历史与Promise原理分析

jimhs / 2821人阅读

摘要:异步的发展历史写法意为回调函数,即异步操作执行完后触发执行的函数,例如当请求完成时就会触发函数。实例生成以后,可以用方法分别指定状态和状态的回调函数。第一个回调函数是对象的状态变为时调用,第二个回调函数是对象的状态变为时调用。

关于异步

所谓"异步",简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。

比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。

相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。

简单的说同步就是大家排队工作,异步就是大家同时工作。如果你还不太明白异步与同步,多看看JS基础的文章。

异步的发展历史 1.CallBack写法

CallBack意为“回调函数”,即异步操作执行完后触发执行的函数,例如:

$.get("http://api.xxxx.com/xxx",callback);

当请求完成时就会触发callback函数。

callback可以完成异步操作,但是经历过JQuery时代的人应该都对某一种需求折磨过,举个例子:项目要求前端ajax请求后端接口列表类型名称,然后在用类型名称ajax请求列表id,在用id请求列表具体内容,最后代码大概是这样的

$.ajax({
    url: "type",
    data:1,
    success: function (a) {
        $.ajax({
            url: "list",
            data:a,
            success: function (b) {
                $.ajax({
                    url: "content",
                    data:b,
                    success: function (c) {
                        console.log(c)
                    }
                })
            }
        })
    }
})

这是是单纯的嵌套代码,如若再加上业务代码,代码可读性可想而知,如果是开发起来还好,但是后期的维护和修改的难度足以让人疯掉。这就是那个JQuery时代的“回调地狱”问题。

2.Promise

为了解决“回调地狱”问题,提出了Promise对象,并且后来加入了ES6标准,Promise对象简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

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

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

这样采用 Promise,解决“回调地狱”问题,比如连续读取多个文件,写法如下。

var readFile = require("fs-readfile-promise");

readFile(fileA)
.then(function (data) {
  console.log(data.toString());
})
.then(function () {
  return readFile(fileB);
})
.then(function (data) {
  console.log(data.toString());
})
.catch(function (err) {
  console.log(err);
});

可见这种写法要比CallBack写法直观的多。但是,有没有更好的写法呢?

3.Generator 函数

Genrator 函数要用* 来比标识,yield关键字表示暂停。将函数分割出好多个部分,调用一次next就会继续向下执行。返回结果是一个迭代器,迭代器有一个next方法。

function* read() {
    console.log(1);
    let a = yield "123";
    console.log(a);
    let b = yield 9
    console.log(b);
    return b;
}
let it = read();
console.log(it.next("213")); // {value:"123",done:false}
console.log(it.next("100")); // {value:9,done:false}
console.log(it.next("200")); // {value:200,done:true}
console.log(it.next("200")); // {value:200,done:true}

yield后面跟着的是value的值,yield等号前面的是我们当前调用next传进来的值,并且第一次next传值是无效的。

处理异步的时候Generator和Promise搭配使用

let bluebird = require("bluebird");
let fs = require("fs");
let read = bluebird.promisify(fs.readFile);//将readFile转为Promise对象的实例
function* r() {
    let content1 = yield read("./2.promise/1.txt", "utf8");
    let content2 = yield read(content1, "utf8");
    return content2;
}

这样看起来是我们想要的样子,但是只写成这样还不行,想得到r()的结果还要对函数进行处理

function co(it) {
    return new Promise(function (resolve, reject) {
        function next(d) {
            let { value, done } = it.next(d);
            if (!done) {
                value.then(function (data) { // 2,txt
                    next(data)
                }, reject)
            } else {
                resolve(value);
            }
        }
        next();
    });
}
co(r()).then(function (data) {
    console.log(data)//得到r()的执行结果
})

这样的处理方式显然很麻烦,并不是我们想要,我们想要直观的写起来就就像同步函数,而且简便的方式处理异步。有这样的方法吗?

4.async-await函数

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

let bluebird = require("bluebird");
let fs = require("fs");
let read = bluebird.promisify(fs.readFile);

async function r(){
    try{
        let content1 = await read("./2.promise/100.txt","utf8");
        let content2 = await read(content1,"utf8");
        return content2;
    }catch(e){ // 如果出错会catch
        console.log("err",e)
    }
}

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

async函数返回的是promise

r().then(function(data){
    console.log(data);
},function(err){

})

至此,async-await函数已经可以我们满意,以后会不会出现更优秀的方案?以我们广大程序群体的创造力,相信一定会有的。

Promise原理分析

async-await函数其实只是Generator函数的语法糖,而Generator函数的实现方式也是要基于Promise,所以我们队Promise的实现原理进行分析。

Promise对象有以下几种状态:

pending: 初始状态, 既不是 fulfilled 也不是 rejected.

fulfilled: 成功的操作.

rejected: 失败的操作.

在上面了解了Promise的基本用法后,我们先将Promise的框架搭起来

function Promise(executor) { // executor是一个执行函数
    let self = this;
    self.status = "pending";
    self.value = undefined; // 默认成功的值
    self.reason = undefined; // 默认失败的原因
    self.onResolvedCallbacks = []; // 存放then成功的回调
    self.onRejectedCallbacks = []; // 存放then失败的回调
    function resolve(value) { // 成功状态
        
    }
    function reject(reason) { // 失败状态
        
    }
    try {
        executor(resolve, reject)
    } catch (e) { // 捕获的时候发生异常,就直接失败了
        reject(e);
    }
}

Promise.prototype.then = function (onFulfilled, onRjected) {
//then方法
})

接下来当调用成功状态resolve的时候,会改变状态,执行回调函数:

function resolve(value) { // 成功状态
        if (self.status === "pending") {
            self.status = "resolved";
            self.value = value;
            self.onResolvedCallbacks.forEach(function (fn) {
                fn();
            });
        }
    }

reject函数同理

function reject(reason) { // 失败状态
        if (self.status === "pending") {
            self.status = "rejected";
            self.reason = reason;
            self.onRejectedCallbacks.forEach(function (fn) {
                fn();
            })
        }
    }

接下来我们完成then函数

Promise.prototype.then = function (onFulfilled, onRjected) {
    let self = this;
    let promise2; //返回的promise
    if (self.status === "resolved") {
        promise2 = new Promise(function (resolve, reject) {
            
        })
    }
    if (self.status === "rejected") {
        promise2 = new Promise(function (resolve, reject) {
            
        })
    }
    // 当调用then时可能没成功 也没失败
    if (self.status === "pending") {
        promise2 = new Promise(function (resolve, reject) {

        })
    }
    return promise2;
}

Promise允许链式调用,所以要返回一个新的Promise对象promise2

Promise.prototype.then = function (onFulfilled, onRjected) {
    //成功和失败默认不穿给一个函数
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === "function" ? onRjected : function (err) {
        throw err;
    }
    let self = this;
    let promise2; //返回的promise
    if (self.status === "resolved") {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    // x可能是别人promise,写一个方法统一处理
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        })
    }
    if (self.status === "rejected") {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRjected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    
                    reject(e);
                }
            })

        })
    }
    // 当调用then时可能没成功 也没失败
    if (self.status === "pending") {
        promise2 = new Promise(function (resolve, reject) {
            // 此时没有resolve 也没有reject
            self.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRjected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })
    }
    return promise2;
}

在promise2内部定义一个变量x为回调函数的返回值,由于返回值可能会有多种可能的情况,所以我们定义一个resolvePromise函数统一处理

返回值可以分为

promise返回自己 (报错循环引用)

返回promise对象 (根据promise对象调用成功或失败回调函数)

返回普通值 (调用成功回调函数传入返回值)

报错 (调用失败回到传入错误)

function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { 
        return reject(new TypeError("循环引用了"))
    }
    // 判定x是不是一个promise,promise应该是一个对象
    let called; // 表示是否调用过成功或者失败
    if (x !== null && (typeof x === "object" || typeof x === "function")) {
        try { // {then:1}
            let then = x.then;
            if (typeof then === "function") {
                // 成功
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    // y可能还是一个promise,在去解析直到返回的是一个普通值
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失败
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { // 说明是一个普通值
        resolve(x); // 调用成功回调
    }
}

如果返回值为对象或函数,且有then方法,那我们就认为是一个promise对象,去调用这个promise进行递归,直到返回普通值调用成功回调。

最后,再加上一个catch方法,很简单

Promise.prototype.catch = function (callback) {
    return this.then(null, callback)
}

这些就是promise的主要功能的原理,附上完整代码

function Promise(executor) { // executor是一个执行函数
    let self = this;
    self.status = "pending";
    self.value = undefined; // 默认成功的值
    self.reason = undefined; // 默认失败的原因
    self.onResolvedCallbacks = []; // 存放then成功的回调
    self.onRejectedCallbacks = []; // 存放then失败的回调
    function resolve(value) { // 成功状态
        if (self.status === "pending") {
            self.status = "resolved";
            self.value = value;
            self.onResolvedCallbacks.forEach(function (fn) {
                fn();
            });
        }
    }
    function reject(reason) { // 失败状态
        if (self.status === "pending") {
            self.status = "rejected";
            self.reason = reason;
            self.onRejectedCallbacks.forEach(function (fn) {
                fn();
            })
        }
    }
    try {
        executor(resolve, reject)
    } catch (e) { 
        reject(e);
    }
}
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { 
        return reject(new TypeError("循环引用了"))
    }
    let called; 
    if (x !== null && (typeof x === "object" || typeof x === "function")) {
        try { 
            let then = x.then;
            if (typeof then === "function") {
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失败
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { 
        resolve(x); 
    }
}
Promise.prototype.then = function (onFulfilled, onRjected) {
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === "function" ? onRjected : function (err) {
        throw err;
    }
    let self = this;
    let promise2; 
    if (self.status === "resolved") {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        })
    }
    if (self.status === "rejected") {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRjected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    
                    reject(e);
                }
            })

        })
    }

    if (self.status === "pending") {
        promise2 = new Promise(function (resolve, reject) {
            self.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRjected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })
    }
    return promise2;
}

Promise.prototype.catch = function (callback) {
    return this.then(null, callback)
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93366.html

相关文章

  • JavaScript 运行机制--Event Loop详解

    摘要:上代码代码可以看出,不仅函数比指定的回调函数先执行,而且函数也比先执行。这是因为后一个事件进入的时候,事件环可能处于不同的阶段导致结果的不确定。这是因为因为执行完后,程序设定了和,因此阶段不会被阻塞进而进入阶段先执行,后进入阶段执行。 JavaScript(简称JS)是前端的首要研究语言,要想真正理解JavaScript就绕不开他的运行机制--Event Loop(事件环) JS是一门...

    snifes 评论0 收藏0
  • 异步发展流程 —— Generators + co 让异步更优雅

    摘要:遍历器原有的表示集合的数据结构,主要有和,在中又加入了和,这样就有了四种数据集合,还可以组合使用它们,如数组的成员是或,这样就需要一种统一的接口机制,用来处理所有不同的数据结构。 showImg(https://segmentfault.com/img/remote/1460000018998438?w=900&h=431); 阅读原文 Generators 简介 Generato...

    dingda 评论0 收藏0
  • Node_深入浅出Node

    摘要:简介项目命名为就是一个服务器单纯开发一个服务器的想法,变成构建网络应用的一个基本框架发展为一个强制不共享任何资源的单线程,单进程系统。单线程弱点无法利用多核错误会引起整个应用退出,应用的健壮性大量计算占用导致无法继续调用异步。 NodeJs简介 Ryan Dahl项目命名为:web.js 就是一个Web服务器.单纯开发一个Web服务器的想法,变成构建网络应用的一个基本框架.Node发展...

    shinezejian 评论0 收藏0
  • JavaScript 异步

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...

    tuniutech 评论0 收藏0
  • 【16】winter重学前端 - JavaScript执行(一):Promise里的代码为什么比se

    摘要:即使耗时一秒的执行完毕,再的,仍然先于执行了,这很好地解释了微任务优先的原理。把整个代码分割成了个宏观任务,这里不论是秒还是秒,都是一样的。 js实现异步的几种形式 回调函数 事件监听 - 事件驱动模式 发布/订阅 - 观察者模式 Promises对象 js异步历史 一个 JavaScript 引擎会常驻于内存中,它等待着我们把JavaScript 代码或者函数传递给它执行 在 ...

    Vicky 评论0 收藏0

发表评论

0条评论

jimhs

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<