确定的世界 - The Promise's World

确定的世界 - The Promise’s World

—— Promise的逻辑以及在Lua中的实现

本文基于如下项目(ES6 Promise in lua v1.0.1):

https://github.com/aimingoo/Promise

有这样一个世界

有这样的一个世界在持续地向前推进着,这个世界充满着无穷多个选择,也就是说有无穷多的可能;但对每一个选择来说,存在决定/未决两种状态,如果已经决定,则只能有yes/no两种结果之一。这个世界看起来就是下面这个样子:

那么,这个世界是确定的么?

首先,这个世界是有状态的,那些看起来存在y/n两个分支的节点,就存在于这种状态——选择还没有发生,就还有可能。

然而无论某一个节点的选择是什么,这个的世界的结果(的规模)是确定的,它必然演进到世界最下层中的状态的某一个。只不过这个最下层的规模足够大,所以在世界中的“未决”因素足够多的情况下,这个世界就看起来“一切皆有可能”而已。

而这也就是Promise’s World,确定的世界。

什么是已决定的?

到底什么是已“确定”的呢?当你举起手枪对着自己的头的时候,这个行为是“确定”的吗?

答案是:这个行为是“确定”的,你只是没有“决定”而已。这个称为“用枪瞄准自己的头”的系统很确定:要么是死,要么是不死——死或不死都是确定的。在语言中,你可以理解为:布尔值是确定的值,它确定的是yes/no。

布尔值被理解为一个“已决定(它的结果行为)的”系统。同理,所有值类型的数据,所有有已决定结果的数据,都是确定的。这在promise中被这样定义下来:

local function promised_y(self, onFulfilled)
    -- ...
end

-- ...

-- promise for basetype
local number_promise = setmetatable({andThen = promised_y}, promise)
local true_promise   = setmetatable({andThen = promised_y, true}, promise)
local false_promise  = setmetatable({andThen = promised_y, false}, promise)
number_promise.__index = number_promise
nil_promise.andThen = promised_y

Ok,这里的代码的意思是说,number/string/boolean,以及nil这些值的行为(andThen)都是已决定的,指向promised_y()。

javascript的promise规范中,这里被称为.then()方法,而拥有这样一个方法的对象被称为thenable object。这个方法有确定的接口:

function (onFulfilled, onRejected)

但在lua中由于then是保留字,所以只好用andThen作为方法名(也有用next来作为方法名的)。

同样,一个对象(lua中的table)也是已决定的,在promise中它与一般的value并没有不同。因此,在lua中的Promise.resolve(value)实现为如下:

function Promise.resolve(value)
    local valueType = type(value)
    if valueType == ‘nil‘ then
        return nil_promise
    elseif valueType == ‘boolean‘ then
        return value and true_promise or false_promise
    elseif valueType == ‘number‘ then
        return setmetatable({(value)}, number_promise)
    elseif valueType == ‘string‘ then
        return value
    elseif (valueType == ‘table‘) and (value.andThen ~= nil) then
        return value.catch ~= nil and value -- or, we can direct return value
            or setmetatable({catch=promise.catch}, {__index=value})
    else
        return setmetatable({andThen=promised_y, value}, promise)
    end
end

留意这里的string类型,它与其它value略有区别,是因为string类型在lua中正好是有meta type的,因此可以直接通过修改元表来让它“变得与promise object”行为一样。至于其它,就必须包装一下了。

对于对象(object/table)来说,它可能有三种情况:

  • 如果为thenable object(即,有.andThen而没有.catch方法),则给他一个catch()方法;
  • 如果为promise object,则直接返回;
  • 如果为普通object(即,其它样式的lua table),则包装成promise object并返回。

那么,什么是promise object呢?

promise object and Promise class

按照协议,promise框架必须实现promise object和Promise class。参考:ECMAScript 2015(ES6),包括如下这些类方法:

Promise.new(func)

Promise.resolve(value)

Promise.reject(reason)

Promise.all(arr)

Promise.race(arr)

以及对象方法:

promise:andThen(onFulfilled, onRejected)

Promise:catch(onRejected)

五种类方法之任一都将得到一个promise object。确切地说,你也只能通过这五种方法来得到promise object,哪怕只是数字1,也应当这样来写:

local promise_number_1 = Promise.resolve(1)

这些类方法有些“潜在的/隐式地”将值变成promise的能力,例如:

Promise.all(arr)

严格地来说all()要处理的是一个promise object array,为了这个目的,事实上它会将arr中的每个成员都尝试转换(resolve)以得到promise object。因此下面两种方法:

Promise.all([1,2,3]):andThen(..)
-- vs.
Promise.all([
    Promise.resolve(1),
    Promise.resolve(2),
    Promise.resolve(3)
]):andThen(..)

事实上是一样的效果。

而具体到一个promise object,它在lua中描述的结构是一个table(array)。初始化时,它只有一个元素(我通常称之为p[1]):

a_promise_instance = { PENDING }

PENDING表明这个promise是未决的。如果已决定,例如上面提到过的“所有的”值,那么p[1]就存放的是那个具体的值。例如:

-- boolean promise object
{true}, {false}
-- number promise object
{1}, {1.23}, ..
-- object promise object
{{}}, {obj}, ..
-- userdata/function/coroutine/..
{userdata}, {func}, {co}, ..
-- nil promise object
{}
-- string is string, ^^.
‘abcd‘
-- non-promised promise object
{PENDING}

在5种Promise类方法中,promise.resolve()和reject()得到的是一个已决定的、值的promise对象(或者,当传入promise object时,返回的是它自身,注1)。而其它三种方法,得到的都将是一个non-promised的对象——也就是说,这些情况下p[1]存放的是PENDING。

注1: 在JavaScript中,这种情况仍将得到一个新的promise,而lua中得到传的的promise object。这并不会带来使用效果上的差异。

推迟决策:lazy resolver

到目前为止,在我们具体讨论PENDING之前,所谓的promise object,以及整个的promise’s world其实都很简单。但仔细看去,这也不过是最开始所描绘的世界中左边的那一半而已——这一半都是promised,是确知的,已决定的。

整个Promise世界的秘密(或魔法、活力)都在右边那一部分。也就是p[1]中写着PENDING的未决的那些结点。这些结点是推迟决策的,它什么时候到来是未知的,由于状态未决,所以也有不可确知的结果。仍然以那把瞄着你的头的枪为例,板机并不是你在扣着,而是在1公里以外的一个狙击手的手上。现在(当下)的问题是,你处于PENDING状态,既不知道那个狙击手是不是已经被第三者干掉了,还是已经扣下了板机而子弹是在飞过来的路上。PENDING这个状态是未决的,它未决的是你的生死,直到p[1]被填上一个值——如同子弹真正地射入你的头脑,或者邦德站在你的面前告诉你说“他死了”。

p[1]将被填入“射击”这个行为发生的结果(value/result),或这个行为没有发生的原因(reson)。无论二者之一为何,这个p[1]都是你现在(当下)所不确知的,所以尽管我们有千般主意,也只能等待value/reson两种结果被确认。这“千般主意”我们都可以一一想好,并且用promise:andThen()关联给这个promise,但……我们就是得等着结果发生。所以,Promise in lua为此设计了lazy对象,每一个用andThen添加进来的“主意”都是一个lazy,被追加到promise object数组的末尾——好的,我想你已经知道了,就是p[2]..p[n],或称之p[2..n]。

p[2..n]是一个个独立的lazy object。每个lazy表达为三个元素的数组:

{promise, onFulfilled, onRejected}

由于一个PENDING promise是未决的,所以当它决定时至少要做的(第一个) 行为就是将真正的结果填入p[1]。所以由new/all/race这三种方法来创建的(未决的)promise的内部都会调用resolver()来实施这一决策:

function Promise.all(arr)
    local this, result, count = setmetatable({PENDING}, promise), {}, #arr

    -- ...
    resolver(this, result, sure)
    -- ...
end

function Promise.race(arr)
    local this, result, count = setmetatable({PENDING}, promise), {}, #arr

    -- ...
    resolver(this, result, sure)
    -- ...
end

function Promise.new(func)
    local this = setmetatable({PENDING}, promise)
    pcall(func,
        function(value) return resolver(this, value, true) end,
        function(reason) return resolver(this, reason, false) end)
    -- ...
end

而这里的“决策(resolver)”,不过是对确定的结果(value)采用确定的行为——将p[1]赋值,并重置andThen()方法:

local function resolver(this, resolved, sure)
    -- ...
        this[1], this.andThen = resolved, sure and promised_y or promised_n
    -- ...
end

以及推进所有p[2..n]中的行为:

local function resolver(this, resolved, sure)
    -- ...
        for i, lazy in next, this, 1 do     -- 2..n
            pcall(resolver, lazy[1], promised(resolved, (sure and lazy[2] or lazy[3])), sure)
            this[i] = nil
        end
    -- ...

而已。然而考虑到promise规范中允许andThen()返回一个non-promised的promise object,因此resolver()将检测这种状态,并将与这个promise object对应的lazy添加到尾部。

最后的promised

现在,promise’s world中的结点要么是已决的(promised promise)。这种情况下它可能是一个一般值转换过来(Promise.resolve)的,因而只有左侧的边(promise_y),也可能是一个promised promise object,因此具有两条边之一。无论如何,这样的一个promise object的p[1]存放着确定的值(value),而andThen()指向一个确定有结果的行为:promised_y、promised_n,或promised_s。而这三个行为都必然是最终确定的:promised()

local function promised(value, action)
    local ok, result = pcall(action, value)
    return ok and Promise.resolve(result) or Promise.reject(result)
end

local function promised_s(self, onFulfilled)
    return onFulfilled and promised(self, onFulfilled) or self
end

local function promised_y(self, onFulfilled)
    return onFulfilled and promised(self[1], onFulfilled) or self
end

local function promised_n(self, _, onRejected)
    return onRejected and promised(self[1], onRejected) or self
end

要么,就是还未决定的(non-promised promise)。因此它的p[1]中写着PENDING,andThen()指向一个将一切未知塞到p[2..n]的函数——既不是左边的y,也不是右边的n。

而这,就是promise’s world的全部了:

做你所决定的,为那些你所不能决定的做准备。

这一切,要等到PENDING发生变化,推迟决策生效(resolver过程启动)时才会有结果——所以resolver()函数是唯一在yes/no之外,你能看到有promised()调用的地方,那是未来将会发生的一次promised。一旦发生,non-promised was promised。

示例

有一个简单的示例,然而绝大多数lua promise框架都run不过。试试看吧:

---
-- A完成后,根据a做BCD三件事,再根据BCD的结果做E。
---

Promise = require(‘Promise‘)

A = function() return 10 end
B = function(a) print(a * 2) end
C = function(a)
    print(a * 4)
    return Promise.resolve(‘ok‘)
end
D = function(a) print(a * 3) end
E = function(result)
    local b, c, d = unpack(result)
    print(b, c, d)
    return Promise.reject(‘FIRE‘)
end

-- promise_A = Promise.resolve(A())
promise_A = Promise.new(function(resolve, reject)
    local ok, result = pcall(A)
    return (ok and resolve or reject)(result)
end)
promise_B = promise_A:andThen(B)
promise_C = promise_A:andThen(C)
promise_D = promise_A:andThen(D)

promises = {promise_B, promise_C, promise_D}
Promise.all(promises)
    :andThen(E)
    :catch(function(reson)
        print(reson)
    end)

版权声明:本文为博主原创文章,未经博主允许不得转载。

确定的世界 - The Promise's World

时间: 2024-08-29 17:34:04

确定的世界 - The Promise's World的相关文章

Promise的前世今生和妙用技巧

浏览器事件模型和回调机制 JavaScript作为单线程运行于浏览器之中,这是每本JavaScript教科书中都会被提到的.同时出于对UI线程操作的安全性考虑,JavaScript和UI线程也处于同一个线程中.因此对于长时间的耗时操作,将会阻塞UI的响应.为了更好的UI体验,应该尽量的避免JavaScript中执行较长耗时的操作(如大量for循环的对象diff等)或者是长时间I/O阻塞的任务.所以在浏览器中的大多数任务都是异步(无阻塞)执行的,例如:鼠标点击事件.窗口大小拖拉事件.定时器触发事件

【javascript】异步编年史,从“纯回调”到Promise

异步和分块——程序的分块执行 一开始学习javascript的时候, 我对异步的概念一脸懵逼, 因为当时百度了很多文章,但很多各种文章不负责任的把笼统的描述混杂在一起,让我对这个 JS中的重要概念难以理解, “异步是非阻塞的”, “Ajax执行是异步的”, "异步用来处理耗时操作"....  “可异步到底是什么?” 后来我发现,其实理解异步最主要的一点,就是记住: 我们的程序是分块执行的. 分成两块, 同步执行的凑一块, 异步执行的凑一块,搞完同步,再搞异步 废话不多说, 直接上图:

异步编程promise

promise不是angular首创的,作为一种编程模式,它出现在--1976年,比js还要古老得多.promise全称是 Futures and promises.具体的可以参见 http://en.wikipedia.org/wiki/Futures_and_promises . 而在javascript世界中,一个广泛流行的库叫做Q 地址是https://github.com/kriskowal/q 而angular中的$q就是从它引入的.promise解决的是异步编程的问题,对于生活在同

AngulaJS实战总结, 带你进入AngularJS世界(待续)

AngulaJS实战总结, 带你进入AngularJS世界(待续) 使用AngularJS  进行Hybrid App 开发已经有一年多时间了,这里做一个总结. 一.AngularJS 初始化加载流程 1.浏览器载入HTML,然后把它解析成DOM.2.浏览器载入angular.js脚本.3.AngularJS等到DOMContentLoaded事件触发.4.AngularJS寻找ng-app指令,这个指令指示了应用的边界.5.使用ng-app中指定的模块来配置注入器($injector).6.注

Promise 初步

在JavaScript的世界中,所有代码都是单线程执行的. 由于这个"缺陷",导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行.异步执行可以用回调函数实现: function callback() { console.log('Done'); } console.log('before setTimeout()'); setTimeout(callback, 1000); // 1秒钟后调用callback函数 console.log('after setTimeo

浅谈ES6原生Promise

浅谈ES6原生Promise 转载 作者:samchowgo 链接:https://segmentfault.com/a/1190000006708151 ES6标准出炉之前,一个幽灵,回调的幽灵,游荡在JavaScript世界. 正所谓: 世界本没有回调,写的人多了,也就有了})})})})}). Promise的兴起,是因为异步方法调用中,往往会出现回调函数一环扣一环的情况.这种情况导致了回调金字塔问题的出现.不仅代码写起来费劲又不美观,而且问题复杂的时候,阅读代码的人也难以理解. 举例如下

海外优秀资讯抢先看12:世界上最薄的硅材料将为您带来引爆眼球的更快,更小,更高效的计算机芯片

One-atom-thin 'silicene' silicon transistors invented 原子大小数量级的硅烯晶体管宣告诞生 World's thinnest silicon material promises dramatically faster, smaller, more efficient computer chips 世界上最薄的硅材料将有望带来引人注目的更快,更小,更高效的计算机芯片 February 5, 2015 2015年2月5号 The first tra

Angular JS中 Promise用法

一.Promise形象讲解A promise不是angular首创的,作为一种编程模式,它出现在1976年,比js还要古老得多.promise全称是 Futures and promises. 而在javascript世界中,一个广泛流行的库叫做Q 地址是https://github.com/kriskowal/q 而angular中的$q就是从它引入的.promise解决的是异步编程的问题,对于生活在同步编程世界中的程序员来说,它可能比较难于理解,这也构成了angular入门门槛之一,以下将用

形象的讲解angular中的$q与promise(转)

以下内容摘自http://www.ngnice.com/posts/126ee9cf6ddb68 promise不是angular首创的,作为一种编程模式,它出现在……1976年,比js还要古老得多.promise全称是 Futures and promises.具体的可以参见 http://en.wikipedia.org/wiki/Futures_and_promises . 而在javascript世界中,一个广泛流行的库叫做Q 地址是https://github.com/kriskowa