确定的世界 - 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)