资讯专栏INFORMATION COLUMN

再谈Promise

chenjiang3 / 2893人阅读

摘要:方法完成回调注册模式下,对象通过方法调用,注册完成态和失败态的回调函数。这些回调函数组成一个回调队列,处理的值。调用实例的方法,能使注册的回调队列中的回调函数依次执行。

之前写了一篇关于ES6原生Promise的文章。近期又读朴灵的《深入浅出Node》,里面介绍了一个Promise/Deferred模式。

Promise是解决异步问题的利器。它其实是一种模式。Promise有三种状态,未完成态、完成态、失败态,相信大家一定不陌生,Promise对象允许使用.then的形式,将回调放到IO操作等异步方法的主体之外,使代码优美不少。

下面我结合《深入浅出Node》,介绍一下如何用ES5实现Promise/Deferred模式。相信研究完该实现代码之后,我们会对Promise的理解更进一步。

Promise

then方法完成回调注册

Promise/Deferred模式下,Promise对象通过then方法调用,注册完成态和失败态的回调函数。

由于then方法支持链式回调,因此then方法的返回值一定也是Promise对象,我们在此简单的返回自身,也就是this

那么一定有人要问了:then中的回调函数,可能返回一个新的Promise对象,此后的then调用是否是在新的Promise对象上调用的呢?

答案是:不一定

这个问题其实困扰我很久,直到看了Promise的实现代码我才想明白。其实then方法的调用,只不过是注册了完成态和失败态下的回调函数而已。这些回调函数组成一个回调队列,处理resolve的值。

Promise构造函数注册回调队列

Promise构造函数,给每个实例一个queue属性,将then方法注册的回调队列,保存在Promise实例的回调队列中。

代码

var Promise = function(){
    this.queue = [];
} 

Promise.prototype.then = function(fulfilledHandler, unfulfilledHandler){
    var handler = {};
    if (typeof fulfilledHandler === "function"){
        handler.fulfilled = fulfilledHandler;
    }
    if (typeof unfulfilledHandler === "function"){
        handler.unfulfilled = unfulfilledHandler;
    }
    this.queue.push(handler);
    return this;
}

我们看到,Promise的代码很简单,只是通过then方法将一系列的回调函数push到队列中而已。Promise实例暴露给用户的也只有一个then方法。

这样我们就可以这样调用了:

promise.then(fulfilledFunc1, unfulfilledFunc1)
    .then(fulfilledFunc2, unfulfilledFunc2)
    .then(fulfilledFunc3, unfulfilledFunc3)

那么如何进行状态转换呢?下面我就来讲一下带有resolve方法(reject方法同理,下面均以resolve举例)的Deferred。

Deferred

Deferred实例决定Promise实例的状态

每个Deferred实例的对应一个Promise实例。调用Deferred实例的resolve方法,能使Promise注册的回调队列中的回调函数依次执行。

先写部分代码:

var Deferred = function(){
    this.promise = new Promise();
}

Deferred.protoype.resolve = function(val){
    var handler, value = val;
    while(handler = this.promise.queue.shift()){
        if (handler && handler.fulfilled){
            value = handler.fulfiller(value) && value;
        }
    }
}

这样我们就能使用Deferred实例返回Promise实例,并且使用Deferred实例的resolve方法来触发Promise实例的完成态回调,并且将上一个回调如果有返回值,我们将该返回值作为新的resolve值传递给后面的回调。

处理回调方法返回的Promise实例

根据Promise模式,回调函数返回Promise实例时,下一个then()中的回调处理的是新的Promise实例。

在之前的代码实现中,then方法注册了一系列的回调函数,这些回调函数应该处理新的promise实例。这里我们用了一个小技巧,见代码:

Deferred.protoype.resolve = function(val){
    var handler, value = val;
    while(handler = this.promise.queue.shift()){
        if (handler && handler.fulfilled){
            value = handler.fulfiller(value) && value;
            // 修改之处在这里:
            if (value && value.isPromise){
                value.queue = this.promise.queue;
                // 最后再加一个小技巧
                this.promise = value;
                
                return;
            }
        }
    }
}

我们将返回promise实例之后的回调列表原封不动的注册到返回的promise中,这样就保证之前then注册的回调队列能继续调用。最后的小技巧可以使旧的deferred实例对应新的promise实例,这样可以继续使用deferred.resolve方法。

为了判断实例是否是Promise实例,这里简单的修改Promise构造函数:

var Promise = function(){
    this.queue = [];
    this.isPromise = true;
} 

封装callback方法用于异步调用

Promise之所以是解决异步的利器,一方面是then方法的链式调用,一方面也是因为resolve方法可以异步调用,触发回调队列。

由于以NodeJS为标志的异步方法其回调函数类似于这样:

asyncFunction(param, function(err, data){
    // do something...
});

我们可以封装一个自己的callback方法,用于异步触发resolve方法。

Deferred.prototype.callback = function(err, data){
    if (err){
        this.reject(err);
    }
    this.resolve(data);
}    

此后我们可以这样promisify一个异步函数:

var async = function(param){
    var defer = new Deferred();
    var args = Array.prototype.silce.call(arguments);
    args.push(defer.callback);
    asyncFunc.apply(null, args);
    return defer.promise;
}
Promisify

由上面的promisify思路,我们写一个更一般化的promisify函数:

var promisify = function(method){
    return function(){
        var defer = new Deferred();
        var args = Array.prototype.silce.call(arguments, 1);
        args.push(defer.callback);
        asyncFunc.apply(null, args);
        return defer.promise;
    }
}

举一个Node中文件操作的例子:

readFile = promisify(fs.readFile);

readFile("file1.txt", "utf8").then(function(file1){
    return readFile(file1.trim(), "utf8");
}).then(function(file2){
    console.log(file2);
})

俩字:优雅。

结束

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

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

相关文章

  • 再谈 JavaScript 异步编程

    摘要:随着前端的发展,异步这个词真是越来越常见了。真正带来革命性改变的是规范。借助,我们可以这样完成异步任务好棒写起来像同步处理的函数一样别着急,少年。总结以上就是笔者总结的几种异步编程模式。 随着前端的发展,异步这个词真是越来越常见了。假设我们现在有这么一个异步任务: 向服务器发起数次请求,每次请求的结果作为下次请求的参数。 来看看我们都有哪些处理方法: Callbacks ...

    RobinQu 评论0 收藏0
  • 再谈express与koa的对比

    摘要:以前其实写过一篇和的对比但是后来发现里面有不少谬误所以一直惦记着纠正一下之前的错误尤其关于中间件部分的对比这里的就拿更加简单的代替的执行流程通常我们都说的中间件模型是线性的也就是一个一个往下执行的如下图这么说当然是没错的但是当我们执行下面代 以前其实写过一篇express和koa的对比, 但是后来发现里面有不少谬误. 所以一直惦记着纠正一下之前的错误, 尤其关于中间件部分的对比. 这里...

    phodal 评论0 收藏0
  • 理解JS中的Event Loop机制

    摘要:前言前几天在理解的事件环机制中引发了我对浏览器里的好奇。接下来理解浏览器中的,先看一张图堆和栈堆是用户主动请求而划分出来的内存区域,比如你,就是将一个对象存入堆中,可以理解为存对象。废话不多说,直接上图个人理解。参考资料运行机制详解再谈 前言 前几天在理解node的事件环机制中引发了我对浏览器里Event Loop的好奇。我们都知道javascript是单线程的,任务是需要一个一个按顺...

    MASAILA 评论0 收藏0
  • 前端进阶系列(八):JS执行机制

    摘要:一直以来,对的执行机制都是模棱两可,知道今天看了文章这一次,彻底弄懂执行机制和的规范和实现,才对的执行机制有了深入的理解,下面是我的学习总结。个要点是单线程语言是的执行机制,为了实现主线程的不阻塞,就这么诞生了。 一直以来,对JS的执行机制都是模棱两可,知道今天看了文章—《这一次,彻底弄懂JavaScript执行机制》和《Event Loop的规范和实现》,才对JS的执行机制有了深入的...

    JackJiang 评论0 收藏0
  • 简要总结microtask和macrotask

    摘要:众所周知和都属于上述异步任务的一种那到底为什么和会有顺序之分这就是我想分析总结的问题所在了和的作用是为了让浏览器能够从内部获取的内容并确保执行栈能够顺序进行。只要执行栈没有其他在执行,在每个结束时,队列就会在回调后处理。 前言 我是在做前端面试题中看到了setTimeout和Promise的比较,然后第一次看到了microtask和macrotask的概念,在阅读了一些文章之后发现没有...

    yexiaobai 评论0 收藏0

发表评论

0条评论

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